Conversation
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.cursor/rules/notes.mdc (1)
1-7: Add main title heading and use kebab-case filename; populate frontmatter description.The file is missing a main title heading and doesn't follow cursor rule conventions. The filename should be descriptive and in kebab-case, and the YAML frontmatter description field should be populated.
Apply the following changes:
Rename the file from
notes.mdctodeveloper-notes.mdc(or similar descriptive kebab-case name).Update the YAML frontmatter and add a main title heading:
--- -description: +description: Development guidelines for project contributors globs: alwaysApply: true --- +# Developer Notes +
- Restructure the content with proper markdown formatting (headings for sections):
+## Guidelines + - Do not try and run the project via `dev` or `build` command unless I explicitly ask you to. - Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments.As per coding guidelines, cursor rule files must have a main title heading and structured content with step-by-step instructions, code examples, and guidelines.
♻️ Duplicate comments (1)
apps/web/utils/outlook/message.ts (1)
115-132: Fix empty-query edge case insanitizeOutlookSearchQuery(still present) and confirm KQL helper behavior
sanitizeKqlValueand the KQL short-circuit look good, but the earlier issue with queries that sanitize to empty still exists in the non-KQL path:
- Example: input
"???"→normalized = "???"- After disallowed-char and whitespace normalization:
sanitized = ""- Escaping keeps it empty, then it is wrapped as
"\"\"", which is truthyeffectiveSearchQuery = cleanedSearchQuery || undefinedwill use""as a literal phrase query instead of treating it as “no query”This is the same scenario flagged in the previous review comment and is still reachable when the query is not KQL-shaped. To align behavior with expectations (“no usable search term” → no
$search), you should short‑circuit when the cleaned value becomes empty, before escaping/quoting.Suggested fix:
function sanitizeOutlookSearchQuery(query: string): { sanitized: string; wasSanitized: boolean; } { const normalized = query.trim(); if (!normalized) { return { sanitized: "", wasSanitized: false }; } @@ // Remove disallowed characters let sanitized = normalized .replace(OUTLOOK_SEARCH_DISALLOWED_CHARS, " ") .replace(/\s+/g, " ") .trim(); + // If nothing remains after removing disallowed characters and collapsing + // whitespace, treat this as “no query” instead of issuing an empty phrase search. + if (!sanitized) { + return { sanitized: "", wasSanitized: false }; + } + // Escape backslashes and double quotes for KQL sanitized = sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); // Wrap in double quotes to treat as literal phrase search // This prevents KQL from interpreting special characters like - . and numbers sanitized = `"${sanitized}"`; return { sanitized, - wasSanitized: true, // Always true now since we're wrapping in quotes + wasSanitized: true, }; }This keeps your new KQL handling intact (KQL queries bypass this path entirely) while preventing accidental empty phrase searches for fully stripped user input.
Also applies to: 143-168
🧹 Nitpick comments (14)
apps/web/utils/actions/ai-rule.ts (1)
44-45: Switch to SafeError here is correct; consider adding logging for consistencyUsing
SafeErrorinstead of a genericErrorfor these user-facing guard failures is aligned with the project’s security guidance and the rest of this file’s patterns. The messages are generic enough and avoid leaking internal details.For consistency with
saveRulesPromptAction/createRulesAction, you might also log these cases before throwing (e.g.,logger.error("Email account not found")/logger.error("Provider not found")) so missing-context issues are easier to trace in logs.Based on learnings, this matches the recommendation to prefer
SafeErrorfor user-visible failures.apps/web/utils/cron.ts (1)
6-34: Stronger handling for missingCRON_SECRETis good; consider mirroring it ingetCronSecretHeader.The early return + log in
hasCronSecretandhasPostCronSecretis a solid hardening step and avoids treating a missing secret as just another unauthorized request. One follow-up to consider is updatinggetCronSecretHeader()to either log and throw or log and returnnullwhenenv.CRON_SECRETis unset, so you don’t accidentally sendAuthorization: Bearer undefinedfrom misconfigured internal callers.apps/web/utils/ai/meeting-briefs/generate-briefing.ts (1)
16-61: Consider adding error handling for LLM generation failures.Per coding guidelines, LLM feature functions should implement error handling with try/catch and consider retry logic for transient failures. Currently, errors propagate directly to the caller.
+import { withRetry } from "@/utils/retry"; +import { createScopedLogger } from "@/utils/logger"; + +const logger = createScopedLogger("ai/meeting-briefings"); + export async function aiGenerateMeetingBriefing({ briefingData, emailAccount, }: { briefingData: MeetingBriefingData; emailAccount: EmailAccountWithAI; }): Promise<string> { + try { // ... existing implementation ... - return result.object.briefing; + return result.object.briefing; + } catch (error) { + logger.error("Failed to generate meeting briefing", { error }); + throw error; + } }apps/web/utils/redis/perplexity-research.ts (1)
83-84: Consider hashing email in log messages to avoid PII exposure.Per security guidelines, logs should not contain PII like email addresses. Consider using a hash or omitting the email.
} catch (error) { - logger.error("Failed to get cached perplexity research", { email, error }); + logger.error("Failed to get cached perplexity research", { error }); return null; }apps/web/utils/meeting-briefs/process.ts (1)
89-117: Consider adding ownership validation to the emailAccount query.Per security guidelines, database queries should include ownership filters. While
emailAccountIdlikely comes from authenticated middleware, adding auserIdfilter provides defense in depth.If the
userIdis available in context, consider:const emailAccount = await prisma.emailAccount.findUnique({ - where: { id: emailAccountId }, + where: { + id: emailAccountId, + // Add userId if available for defense in depth + }, select: { // ... }, });apps/web/utils/meeting-briefs/gather-context.ts (1)
103-109: Consider masking or hashing email in logs.Logging the raw
attendee.emailcould expose PII in logs. Consider using a hashed or truncated form for traceability without full exposure, consistent with privacy best practices.}).catch((error) => { logger.warn("Failed to research guest", { - email: attendee.email, + emailHash: attendee.email.slice(0, 3) + "***", error, }); return null; }),apps/web/utils/calendar/providers/google-events.ts (1)
91-122: LGTM with a minor observation.The event parsing logic correctly extracts all relevant fields and handles video conference links from multiple sources. The fallback to
Date.now()for missing dates ensures the function always returns valid Date objects.Consider logging a warning when
event.idis missing (Line 108), as an empty string ID might cause issues in downstream code that expects unique identifiers:+ if (!event.id) { + logger.warn("Event missing ID", { summary: event.summary }); + } + return { id: event.id || "",Alternatively, you could generate a temporary ID or throw an error if the ID is critical for your use case.
apps/web/app/(app)/[emailAccountId]/briefs/page.tsx (1)
282-290: Status badge may not handle all enum values gracefully.The current implementation only distinguishes between
"SENT"(green) and everything else (red). According to the migration summary, the enum will be extended to includePENDINGandSKIPPEDstatuses. Consider handling these cases explicitly for better UX.<span - className={`text-xs px-2 py-1 rounded ${ - briefing.status === "SENT" - ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" - : "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200" - }`} + className={cn( + "text-xs px-2 py-1 rounded", + briefing.status === "SENT" && "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200", + briefing.status === "FAILED" && "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200", + briefing.status === "PENDING" && "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200", + briefing.status === "SKIPPED" && "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200", + )} >apps/web/utils/meeting-briefs/send-briefing.ts (1)
46-62: Consider validatingRESEND_FROM_EMAILwhen using Resend.The code checks for
RESEND_API_KEYbut doesn't verify thatRESEND_FROM_EMAILis also configured. IfRESEND_FROM_EMAILis undefined or empty, the email send will likely fail with an unclear error.// Try Resend first if configured - if (env.RESEND_API_KEY) { + if (env.RESEND_API_KEY && env.RESEND_FROM_EMAIL) { log.info("Sending briefing via Resend");Alternatively, add explicit validation with a clearer error message before attempting to send.
apps/web/app/api/user/calendar/upcoming-events/route.ts (1)
22-26: Consider handling individual provider failures gracefully.If one calendar provider fails (e.g., expired token),
Promise.allwill reject the entire request. Consider usingPromise.allSettledto return events from successful providers while logging failures.- const providerEvents = await Promise.all( - providers.map(async (provider) => { - return provider.fetchEvents({ maxResults: 3 }); - }), - ); + const results = await Promise.allSettled( + providers.map((provider) => provider.fetchEvents({ maxResults: 3 })), + ); + + const providerEvents = results + .filter((r): r is PromiseFulfilledResult<Awaited<ReturnType<typeof providers[0]['fetchEvents']>>> => r.status === "fulfilled") + .map((r) => r.value);apps/web/prisma/schema.prisma (1)
937-952: Consider addingupdatedAtfield for consistency.Most models in the schema include
updatedAt DateTime @updatedAtfor tracking modifications. This is useful for debugging and auditing, especially if thestatusfield changes over time.model MeetingBriefing { id String @id @default(cuid()) createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt calendarEventId String // External event ID from Google/Microsoftapps/web/utils/calendar/providers/microsoft-events.ts (2)
55-63: Extract duplicate type definition.The
MicrosoftEventtype is defined inline twice with slightly different fields. Extract this to a single shared type at the top of the file to eliminate duplication and ensure consistency.Apply this diff:
+type MicrosoftEvent = { + id?: string; + subject?: string; + bodyPreview?: string; + start?: { dateTime?: string }; + end?: { dateTime?: string }; + attendees?: Array<{ + emailAddress?: { address?: string; name?: string }; + }>; + location?: { displayName?: string }; + webLink?: string; + onlineMeeting?: { joinUrl?: string }; + onlineMeetingUrl?: string; +}; + export class MicrosoftCalendarEventProvider implements CalendarEventProvider { private readonly connection: MicrosoftCalendarConnectionParams; constructor(connection: MicrosoftCalendarConnectionParams) { this.connection = connection; } private async getClient(): Promise<Client> { return getCalendarClientWithRefresh({ accessToken: this.connection.accessToken, refreshToken: this.connection.refreshToken, expiresAt: this.connection.expiresAt, emailAccountId: this.connection.emailAccountId, }); } async fetchEventsWithAttendee({ attendeeEmail, timeMin, timeMax, maxResults, }: { attendeeEmail: string; timeMin: Date; timeMax: Date; maxResults: number; }): Promise<CalendarEvent[]> { const client = await this.getClient(); const response = await client .api("/me/calendar/events") .filter( `start/dateTime ge '${timeMin.toISOString()}' and end/dateTime le '${timeMax.toISOString()}'`, ) .top(maxResults * 3) // Fetch more to filter by attendee .orderby("start/dateTime") .get(); const events = response.value || []; - type MicrosoftEvent = { - id?: string; - subject?: string; - start?: { dateTime?: string }; - end?: { dateTime?: string }; - attendees?: Array<{ - emailAddress?: { address?: string; name?: string }; - }>; - }; - // Filter to events that have this attendee return events .filter((event: MicrosoftEvent) => event.attendees?.some( (a) => a.emailAddress?.address?.toLowerCase() === attendeeEmail.toLowerCase(), ), ) .slice(0, maxResults) .map((event: MicrosoftEvent) => this.parseEvent(event)); } async fetchEvents({ timeMin = new Date(), timeMax, maxResults, }: { timeMin?: Date; timeMax?: Date; maxResults?: number; }): Promise<CalendarEvent[]> { const client = await this.getClient(); const filterParts = [ timeMin ? `start/dateTime ge '${timeMin.toISOString()}'` : "", timeMax ? `end/dateTime le '${timeMax.toISOString()}'` : "", ].filter(Boolean); const response = await client .api("/me/calendar/events") .filter(filterParts.join(" and ")) .top(maxResults || 100) .orderby("start/dateTime") .get(); const events = response.value || []; - type MicrosoftEvent = { - id?: string; - subject?: string; - bodyPreview?: string; - start?: { dateTime?: string }; - end?: { dateTime?: string }; - attendees?: Array<{ - emailAddress?: { address?: string; name?: string }; - }>; - location?: { displayName?: string }; - webLink?: string; - onlineMeeting?: { joinUrl?: string }; - onlineMeetingUrl?: string; - }; - return events.map((event: MicrosoftEvent) => this.parseEvent(event)); }Also applies to: 103-116
121-134: Update method signature to use extracted type.After extracting the
MicrosoftEventtype (per previous comment), update theparseEventmethod signature to use it instead of the inline type definition.Apply this diff:
- private parseEvent(event: { - id?: string; - subject?: string; - bodyPreview?: string; - start?: { dateTime?: string }; - end?: { dateTime?: string }; - attendees?: Array<{ - emailAddress?: { address?: string; name?: string }; - }>; - location?: { displayName?: string }; - webLink?: string; - onlineMeeting?: { joinUrl?: string }; - onlineMeetingUrl?: string; - }) { + private parseEvent(event: MicrosoftEvent) { return {apps/web/utils/email/microsoft.ts (1)
11-12: Normalize participant email once and reuse for both KQL search and header comparisonThe overall
getThreadsWithParticipantflow is solid (participants KQL + header-level verification + per‑thread error handling), but you currently:
- Sanitize
participantEmailfor KQL viasanitizeKqlValue, and- Compare headers against the raw
participantEmail(lowercased) instead of a normalized address.If callers ever pass display‑style values like
"Name <user@example.com>", the KQL search may still work, but header comparisons againstparticipantLowerwill fail even thoughextractEmailAddresson headers returns just"user@example.com".You can make this more robust by normalizing once and reusing that value:
-import { - getMessage, - getMessages, - queryBatchMessages, - getFolderIds, - convertMessage, - MESSAGE_SELECT_FIELDS, - sanitizeKqlValue, -} from "@/utils/outlook/message"; +import { + getMessage, + getMessages, + queryBatchMessages, + getFolderIds, + convertMessage, + MESSAGE_SELECT_FIELDS, + sanitizeKqlValue, +} from "@/utils/outlook/message"; @@ async getThreadsWithParticipant(options: { participantEmail: string; maxThreads?: number; }): Promise<EmailThread[]> { - const { participantEmail, maxThreads = 5 } = options; + const { participantEmail, maxThreads = 5 } = options; + + const participantAddress = + extractEmailAddress(participantEmail) || participantEmail.trim(); + if (!participantAddress) { + return []; + } @@ - // - const sanitizedEmail = sanitizeKqlValue(participantEmail); - const searchQuery = `participants:${sanitizedEmail}`; + // + const sanitizedEmail = sanitizeKqlValue(participantAddress); + if (!sanitizedEmail) { + return []; + } + const searchQuery = `participants:${sanitizedEmail}`; @@ - const participantLower = participantEmail.toLowerCase().trim(); + const participantLower = participantAddress.toLowerCase(); @@ - const fromEmail = extractEmailAddress(h.from || "").toLowerCase(); + const fromEmail = extractEmailAddress(h.from || "").toLowerCase(); if (fromEmail === participantLower) return true; @@ - const toAddresses = (h.to || "") + const toAddresses = (h.to || "") .split(",") .map((addr) => extractEmailAddress(addr.trim()).toLowerCase()) @@ - const ccAddresses = (h.cc || "") + const ccAddresses = (h.cc || "") .split(",") .map((addr) => extractEmailAddress(addr.trim()).toLowerCase())This keeps your KQL query safe, ensures header comparisons succeed for common input formats, and cheaply handles pathological inputs (early return if no usable address).
Also applies to: 870-943
| const log = logger.with({ | ||
| emailAccountId: emailAccount.id, | ||
| email: emailAccount.email, | ||
| }); |
There was a problem hiding this comment.
Consider hashing the email in logs to comply with PII guidelines.
Per coding guidelines, logging user identifiers like emails can be a compliance/privacy concern. The logger already includes emailAccountId which should be sufficient for debugging.
const log = logger.with({
emailAccountId: emailAccount.id,
- email: emailAccount.email,
});📝 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 log = logger.with({ | |
| emailAccountId: emailAccount.id, | |
| email: emailAccount.email, | |
| }); | |
| const log = logger.with({ | |
| emailAccountId: emailAccount.id, | |
| }); |
🤖 Prompt for AI Agents
In apps/web/app/api/meeting-briefs/route.ts around lines 48-51, the logger
currently includes the raw email which is PII; replace the plain email with a
non-reversible hashed identifier (or remove it) to comply with PII guidelines.
Compute a deterministic hash (e.g., HMAC-SHA256 using the app secret or at
minimum SHA-256) of emailAccount.email and pass that hashed value as hashedEmail
to logger.with while keeping emailAccountId unchanged; ensure the hashing
happens before logging and that no plaintext email is written to any logs or
error messages.
| function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { | ||
| return ( | ||
| <div | ||
| role="list" | ||
| data-slot="item-group" | ||
| className={cn("group/item-group flex flex-col", className)} | ||
| {...props} | ||
| /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Add role="listitem" to Item component for ARIA compliance.
ItemGroup uses role="list" (line 11), but the Item component does not include role="listitem". According to ARIA specifications, elements with role="list" must contain elements with role="listitem" to properly convey list semantics to assistive technologies.
Apply this diff to add the required role to Item:
function Item({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div";
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
+ role="listitem"
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
);
}As per coding guidelines: "Include all required ARIA attributes for elements with ARIA roles."
Also applies to: 54-72
🤖 Prompt for AI Agents
In apps/web/components/ui/item.tsx around lines 8-17 (and also apply the same
change to the Item implementation at lines 54-72), the ItemGroup uses
role="list" but the Item component lacks role="listitem"; update the Item
component to include role="listitem" on the root element (ensuring existing
props and className are preserved) so each child of the ItemGroup conveys
correct ARIA list-item semantics to assistive technologies.
| const itemVariants = cva( | ||
| "group/item [a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]", | ||
| { | ||
| variants: { | ||
| variant: { | ||
| default: "bg-transparent", | ||
| outline: "border-border", | ||
| muted: "bg-muted/50", | ||
| }, | ||
| size: { | ||
| default: "gap-4 p-4 ", | ||
| sm: "gap-2.5 px-4 py-3", | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| variant: "default", | ||
| size: "default", | ||
| }, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find Tailwind configuration files
fd -type f -name "tailwind.config.*" -o -name "tailwind.*.js" -o -name "tailwind.*.ts"Repository: elie222/inbox-zero
Length of output: 232
🏁 Script executed:
# Search for custom Tailwind variant definitions or [a]: pattern usage
rg -n "\[a\]:" --type ts --type tsx --type jsRepository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
# Check project root for Tailwind config
ls -la | grep -i tailwindRepository: elie222/inbox-zero
Length of output: 44
🏁 Script executed:
# Find Tailwind config files
find . -maxdepth 3 -name "tailwind.config.*" -o -name "*.config.ts" -o -name "*.config.js" | grep -i tailwindRepository: elie222/inbox-zero
Length of output: 91
🏁 Script executed:
# Search for [a]: pattern in the codebase
rg "\[a\]:" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"Repository: elie222/inbox-zero
Length of output: 349
🏁 Script executed:
# Check for Tailwind configuration in root
ls -la | head -20Repository: elie222/inbox-zero
Length of output: 1225
🏁 Script executed:
# Search for CVA usage and patterns similar to [a]:
rg "group/item|has-\[" --max-count=5 -A 2Repository: elie222/inbox-zero
Length of output: 2414
🏁 Script executed:
# Read the Tailwind config file
cat ./apps/web/tailwind.config.jsRepository: elie222/inbox-zero
Length of output: 6918
🏁 Script executed:
# Search for custom variants or plugins in the config
rg "addVariant|matchVariant|plugin" ./apps/web/tailwind.config.js -A 5Repository: elie222/inbox-zero
Length of output: 230
🏁 Script executed:
# Check if there's any documentation or comments about the [a]: syntax
rg "\[a\]" . -B 3 -A 3Repository: elie222/inbox-zero
Length of output: 1483
Replace [a]: with [&_a]: for correct Tailwind descendant selector syntax.
The classes [a]:hover:bg-accent/50 and [a]:transition-colors on line 34 use invalid Tailwind syntax. The [a]: prefix is neither a standard Tailwind variant nor defined as a custom variant in the project's configuration. To target anchor element children, use the correct arbitrary selector syntax [&_a]: (with the & reference and underscore descendant combinator):
const itemVariants = cva(
- "group/item [a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]",
+ "group/item [&_a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [&_a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]",📝 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 itemVariants = cva( | |
| "group/item [a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]", | |
| { | |
| variants: { | |
| variant: { | |
| default: "bg-transparent", | |
| outline: "border-border", | |
| muted: "bg-muted/50", | |
| }, | |
| size: { | |
| default: "gap-4 p-4 ", | |
| sm: "gap-2.5 px-4 py-3", | |
| }, | |
| }, | |
| defaultVariants: { | |
| variant: "default", | |
| size: "default", | |
| }, | |
| }, | |
| ); | |
| const itemVariants = cva( | |
| "group/item [&_a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [&_a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]", | |
| { | |
| variants: { | |
| variant: { | |
| default: "bg-transparent", | |
| outline: "border-border", | |
| muted: "bg-muted/50", | |
| }, | |
| size: { | |
| default: "gap-4 p-4 ", | |
| sm: "gap-2.5 px-4 py-3", | |
| }, | |
| }, | |
| defaultVariants: { | |
| variant: "default", | |
| size: "default", | |
| }, | |
| }, | |
| ); |
🤖 Prompt for AI Agents
In apps/web/components/ui/item.tsx around lines 33 to 52, the Tailwind arbitrary
selector `[a]:` is invalid; replace the two occurrences
(`[a]:hover:bg-accent/50` and `[a]:transition-colors`) with the correct
descendant selector `[&_a]:hover:bg-accent/50` and `[&_a]:transition-colors` so
the styles target anchor children properly using the `&` reference and
descendant combinator.
| function messageIncludesEmail( | ||
| message: ParsedMessage, | ||
| emailLower: string, | ||
| ): boolean { | ||
| const headers = message.headers; | ||
| return ( | ||
| headers.from.toLowerCase().includes(emailLower) || | ||
| headers.to.toLowerCase().includes(emailLower) || | ||
| (headers.cc?.toLowerCase().includes(emailLower) ?? false) || | ||
| (headers.bcc?.toLowerCase().includes(emailLower) ?? false) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Potential false positives with includes() for email matching.
Using includes() on header strings could match partial email addresses. For example, searching for "john@example.com" would also match "john@example.com.attacker.com". Consider extracting email addresses from headers and comparing exactly.
+import { extractEmailAddresses } from "@/utils/email";
+
function messageIncludesEmail(
message: ParsedMessage,
emailLower: string,
): boolean {
const headers = message.headers;
- return (
- headers.from.toLowerCase().includes(emailLower) ||
- headers.to.toLowerCase().includes(emailLower) ||
- (headers.cc?.toLowerCase().includes(emailLower) ?? false) ||
- (headers.bcc?.toLowerCase().includes(emailLower) ?? false)
- );
+ const allEmails = [
+ ...extractEmailAddresses(headers.from),
+ ...extractEmailAddresses(headers.to),
+ ...extractEmailAddresses(headers.cc ?? ""),
+ ...extractEmailAddresses(headers.bcc ?? ""),
+ ].map(e => e.toLowerCase());
+ return allEmails.includes(emailLower);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web/utils/ai/meeting-briefs/generate-briefing.ts around lines 173-184,
the current messageIncludesEmail uses string.includes which can produce false
positives (e.g., "john@example.com.attacker.com"); instead parse the
from/to/cc/bcc header values into discrete email addresses, normalize each to
lowercase, and check for an exact equality match against emailLower; ensure you
handle comma/semicolon separated lists and optional headers, and return true if
any parsed address equals emailLower, otherwise false.
| return { | ||
| id: event.id || "", | ||
| title: event.subject || "Untitled", | ||
| description: event.bodyPreview || undefined, | ||
| location: event.location?.displayName || undefined, | ||
| eventUrl: event.webLink || undefined, | ||
| videoConferenceLink: | ||
| event.onlineMeeting?.joinUrl || event.onlineMeetingUrl || undefined, | ||
| startTime: new Date(event.start?.dateTime || Date.now()), | ||
| endTime: new Date(event.end?.dateTime || Date.now()), | ||
| attendees: | ||
| event.attendees?.map((attendee) => ({ | ||
| email: attendee.emailAddress?.address || "", | ||
| name: attendee.emailAddress?.name ?? undefined, | ||
| })) || [], | ||
| }; |
There was a problem hiding this comment.
Risky fallback values may mask data issues.
Using Date.now() as the fallback for missing event dates (lines 143-144) could create events with incorrect timestamps, making them appear to start/end "now" rather than indicating a data problem. Similarly, returning an empty string for a missing event ID (line 136) may violate downstream expectations that IDs are non-empty.
Consider throwing an error or logging a warning when required fields are missing, or return null/undefined to make the absence explicit.
Apply this diff to make missing required fields explicit:
return {
- id: event.id || "",
+ id: event.id ?? "",
title: event.subject || "Untitled",
description: event.bodyPreview || undefined,
location: event.location?.displayName || undefined,
eventUrl: event.webLink || undefined,
videoConferenceLink:
event.onlineMeeting?.joinUrl || event.onlineMeetingUrl || undefined,
- startTime: new Date(event.start?.dateTime || Date.now()),
- endTime: new Date(event.end?.dateTime || Date.now()),
+ startTime: event.start?.dateTime ? new Date(event.start.dateTime) : new Date(),
+ endTime: event.end?.dateTime ? new Date(event.end.dateTime) : new Date(),
attendees:
event.attendees?.map((attendee) => ({
email: attendee.emailAddress?.address || "",
name: attendee.emailAddress?.name ?? undefined,
})) || [],
};Additionally, consider adding validation or logging:
if (!event.id || !event.start?.dateTime || !event.end?.dateTime) {
logger.warn("Event missing required fields", {
eventId: event.id,
hasStart: !!event.start?.dateTime,
hasEnd: !!event.end?.dateTime
});
}🤖 Prompt for AI Agents
In apps/web/utils/calendar/providers/microsoft-events.ts around lines 135 to
150, the mapper currently uses Date.now() for missing start/end times and an
empty string for a missing id which can hide data problems; change the mapping
to return undefined (or null) for id, startTime, and endTime when those fields
are absent instead of defaulting to current time/empty string, and add a simple
validation block that logs a warning (using the module logger) when event.id,
event.start?.dateTime, or event.end?.dateTime are missing so the absence is
explicit; optionally, if downstream requires non-null ids, throw a clear error
when id is missing rather than returning a placeholder.
| async getThreadsWithParticipant(options: { | ||
| participantEmail: string; | ||
| maxThreads?: number; | ||
| }): Promise<EmailThread[]> { | ||
| const { participantEmail, maxThreads = 5 } = options; | ||
|
|
||
| const query = `from:${participantEmail} OR to:${participantEmail}`; | ||
| const { threads: gmailThreads } = await getThreadsWithNextPageToken({ | ||
| gmail: this.client, | ||
| q: query, | ||
| maxResults: maxThreads, | ||
| }); | ||
|
|
||
| const threadIds = gmailThreads | ||
| .map((t) => t.id) | ||
| .filter((id): id is string => !!id); | ||
|
|
||
| if (threadIds.length === 0) { | ||
| return []; | ||
| } | ||
|
|
||
| const threads = await getThreadsBatch( | ||
| threadIds, | ||
| getAccessTokenFromClient(this.client), | ||
| ); | ||
|
|
||
| return threads | ||
| .filter((thread) => !!thread.id) | ||
| .map((thread) => ({ | ||
| id: thread.id!, | ||
| messages: | ||
| thread.messages?.map((message) => | ||
| parseMessage(message as MessageWithPayload), | ||
| ) || [], | ||
| snippet: decodeSnippet(thread.snippet), | ||
| })); | ||
| } |
There was a problem hiding this comment.
Expand participant query to include cc: and normalize email input
The new getThreadsWithParticipant works end‑to‑end but has two correctness gaps:
- It only searches
from:andto:, so threads where the participant only appears incc:are silently missed. - It uses
participantEmailverbatim. If callers pass values like"Name <user@example.com>", Gmail search will be less reliable than using the normalized address (you already useextractEmailAddressin other helpers).
You can address both with a small refactor:
- async getThreadsWithParticipant(options: {
- participantEmail: string;
- maxThreads?: number;
- }): Promise<EmailThread[]> {
- const { participantEmail, maxThreads = 5 } = options;
-
- const query = `from:${participantEmail} OR to:${participantEmail}`;
+ async getThreadsWithParticipant(options: {
+ participantEmail: string;
+ maxThreads?: number;
+ }): Promise<EmailThread[]> {
+ const { participantEmail, maxThreads = 5 } = options;
+
+ const email =
+ extractEmailAddress(participantEmail) || participantEmail.trim();
+ if (!email) {
+ return [];
+ }
+
+ // Treat “participant” consistently across providers: match from, to, and cc.
+ const query = `from:${email} OR to:${email} OR cc:${email}`;
@@
- const threads = await getThreadsBatch(
+ const threads = await getThreadsBatch(
threadIds,
getAccessTokenFromClient(this.client),
);
- return threads
- .filter((thread) => !!thread.id)
- .map((thread) => ({
- id: thread.id!,
- messages:
- thread.messages?.map((message) =>
- parseMessage(message as MessageWithPayload),
- ) || [],
- snippet: decodeSnippet(thread.snippet),
- }));
+ return threads
+ .filter((thread) => !!thread.id)
+ .map((thread) => ({
+ id: thread.id!,
+ messages:
+ thread.messages?.map((message) =>
+ parseMessage(message as MessageWithPayload),
+ ) || [],
+ snippet: decodeSnippet(thread.snippet),
+ // Optional: preserve historyId for parity with other thread helpers.
+ historyId: thread.historyId || undefined,
+ }));
}This brings Gmail’s behavior in line with Outlook’s “participant” semantics and hardens the method against non‑raw email inputs.
🤖 Prompt for AI Agents
In apps/web/utils/email/google.ts around lines 911 to 947, the participant
search only uses "from:" and "to:" and passes participantEmail verbatim; update
the logic to first normalize the input with
extractEmailAddress(participantEmail) (fall back to original if extraction
fails), then build the Gmail query to include cc: as well (e.g., `from:... OR
to:... OR cc:...`) so threads where the participant appears only in CC are
returned and searches tolerate "Name <user@example.com>" formats.
| @@ -61,6 +67,11 @@ export async function deleteUser({ userId }: { userId: string }) { | |||
| captureException(error, { extra: { userId } }, userId); | |||
There was a problem hiding this comment.
Third parameter to captureException should be an email address, not userId.
Looking at the captureException signature in apps/web/utils/error.ts (lines 35-47), the third parameter is userEmail?: string which is used to call setUser({ email: userEmail }). Passing userId here is semantically incorrect and may cause confusion in Sentry user identification.
- captureException(error, { extra: { userId } }, userId);
+ captureException(error, { extra: { userId } });The same issue appears on line 72 and line 97. Consider removing the third argument or passing an actual email address if available.
📝 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.
| captureException(error, { extra: { userId } }, userId); | |
| captureException(error, { extra: { userId } }); |
🤖 Prompt for AI Agents
In apps/web/utils/user/delete.ts around lines 67, 72 and 97, the third argument
passed to captureException is userId but captureException's third parameter is
userEmail; replace those incorrect userId arguments by either removing the third
parameter entirely or passing the actual user email (if available in scope) so
setUser receives an email string; update all three calls consistently and ensure
extra context still includes userId in the second-parameter extra object.
| export type MeetingBriefingEmailProps = { | ||
| baseUrl: string; | ||
| emailAccountId: string; | ||
| meetingTitle: string; | ||
| formattedTime: string; // e.g., "2:00 PM" | ||
| videoConferenceLink?: string; | ||
| eventUrl: string; | ||
| briefingContent: string; | ||
| unsubscribeToken: string; | ||
| }; |
There was a problem hiding this comment.
unsubscribeToken prop is declared but never used.
The unsubscribeToken is defined in MeetingBriefingEmailProps but not used in the component. Either use it to generate an unsubscribe link or remove it from the type.
export type MeetingBriefingEmailProps = {
baseUrl: string;
emailAccountId: string;
meetingTitle: string;
formattedTime: string; // e.g., "2:00 PM"
videoConferenceLink?: string;
eventUrl: string;
briefingContent: string;
- unsubscribeToken: string;
};📝 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.
| export type MeetingBriefingEmailProps = { | |
| baseUrl: string; | |
| emailAccountId: string; | |
| meetingTitle: string; | |
| formattedTime: string; // e.g., "2:00 PM" | |
| videoConferenceLink?: string; | |
| eventUrl: string; | |
| briefingContent: string; | |
| unsubscribeToken: string; | |
| }; | |
| export type MeetingBriefingEmailProps = { | |
| baseUrl: string; | |
| emailAccountId: string; | |
| meetingTitle: string; | |
| formattedTime: string; // e.g., "2:00 PM" | |
| videoConferenceLink?: string; | |
| eventUrl: string; | |
| briefingContent: string; | |
| }; |
🤖 Prompt for AI Agents
In packages/resend/emails/meeting-briefing.tsx around lines 13 to 22, the
unsubscribeToken prop is declared in MeetingBriefingEmailProps but never used;
either remove it from the type if not needed, or use it to build an unsubscribe
URL (e.g., `${baseUrl}/unsubscribe?token=${unsubscribeToken}`) and render that
link in the email (preferably in the footer with accessible link text), ensuring
to URL-encode the token and update any callers to pass or stop passing the token
accordingly.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
350-360: Fix global error list to handle nested error objects safelyThe global error banner still maps
Object.values(errors)and assumes each value has a.message, which isn’t true for nested error objects (e.g.,actions,conditions). This can render blank list items and duplicate/undefined keys.Adapting the earlier suggestion, you can map to messages first, filter falsy, and key by index+message:
- description={ - <ul className="list-disc"> - {Object.values(errors).map((error) => ( - <li key={error.message}>{error.message}</li> - ))} - </ul> - } + description={ + <ul className="list-disc"> + {Object.values(errors) + .map((e: any) => e?.message as string | undefined) + .filter((msg): msg is string => !!msg) + .map((msg, i) => ( + <li key={`${i}-${msg}`}>{msg}</li> + ))} + </ul> + }This at least prevents empty entries and key collisions for top‑level field errors.
🧹 Nitpick comments (7)
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx (2)
341-357: Prefer sharedInputcomponent instead of rawTextareaAutosizefor instructionsThe instructions field uses
TextareaAutosizedirectly; elsewhere the app standardizes form fields through theInputcomponent (including autosizing textareas), which centralizes styling and error handling. Consider switching this toInputwithautosizeTextareato keep form UX consistent with the rest of the app.
Based on learnings, this keeps text area handling consistent.
477-478: Tighten tooltip copy for “from/to” filters
Only apply this rule ${filterType} emails from this addressreads oddly for the"from"case (“from emails from this address”). Consider wording likeOnly apply this rule to emails ${filterType} this address…to avoid repetition and be clearer.apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx (3)
4-10: Simplify React Hook Form typings instead of usingReturnType<typeof useForm>You’re importing
useFormas a type and then usingReturnType<typeof useForm<CreateRuleBody>>["..."]to typeregister,watch,setValue, andcontrol. This is harder to read and ties the types directly to the hook implementation.Since you already use
UseFormRegister,UseFormWatch,UseFormSetValue, andControlinActionSteps, you can reuse those here (and optionallyUseFormReturn) for consistency:-import type { - useForm, - Control, - UseFormRegister, - UseFormSetValue, - UseFormWatch, -} from "react-hook-form"; +import type { + Control, + UseFormRegister, + UseFormSetValue, + UseFormWatch, + UseFormReturn, +} from "react-hook-form"; ... function ActionCard({ index, register, watch, setValue, control, @@ -}: { - action: CreateRuleBody["actions"][number]; - index: number; - register: ReturnType<typeof useForm<CreateRuleBody>>["register"]; - watch: ReturnType<typeof useForm<CreateRuleBody>>["watch"]; - setValue: ReturnType<typeof useForm<CreateRuleBody>>["setValue"]; - control: ReturnType<typeof useForm<CreateRuleBody>>["control"]; +}: { + action: CreateRuleBody["actions"][number]; + index: number; + register: UseFormRegister<CreateRuleBody>; + watch: UseFormWatch<CreateRuleBody>; + setValue: UseFormSetValue<CreateRuleBody>; + control: Control<CreateRuleBody>; @@ -function DelayInputControls({ +function DelayInputControls({ index, delayInMinutes, setValue, }: { index: number; delayInMinutes: number | null | undefined; - setValue: ReturnType<typeof useForm<CreateRuleBody>>["setValue"]; + setValue: UseFormReturn<CreateRuleBody>["setValue"]; }) {This keeps the component aligned with the rest of the form code and avoids subtle confusion around
import typeandtypeof useForm.Also applies to: 124-131, 672-680
109-140: Remove unusedactionprop to avoid lint noise
ActionCardaccepts anactionprop but never uses it inside the function. You can drop it from both the prop list and the call site to satisfy no‑unused‑parameters rules and keep the API tighter.
394-399: Route email/content textareas through the sharedInputcomponentThe email content fields are rendered with
TextareaAutosizedirectly. Given the project convention of using the sharedInputcomponent (withautosizeTextareafor multi-line text) for form fields, it would be better to wrap these textareas inInputas well for consistent styling, validation, and future behavior changes.Refactor these to
InputwithautosizeTextarea,rows, andregisterPropsmatching the existing pattern.
Based on learnings, this aligns text area handling with other forms.apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
191-194: Avoidconsole.errorin form submit error handlingThe submit handler logs
console.error(res)on server errors. Project guidelines discourage directconsoleusage; you’re already surfacing the problem viatoastError, so the console log is mostly redundant.Consider removing the
console.erroror routing it through your centralized logging/monitoring instead.
614-619: DeduplicateallowMultipleConditionsbetween RuleForm and ConditionSteps
allowMultipleConditionsis defined here and again inConditionSteps.tsxwith identical logic. Centralizing this helper (e.g., in a small shared utility underutils/rule/) would avoid divergence if the rules for multi-condition support change later.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(7 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleSteps.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/(app)/[emailAccountId]/assistant/RuleSteps.tsx
- apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
🧰 Additional context used
📓 Path-based instructions (14)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use@/path aliases for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Follow consistent naming conventions using PascalCase for components
Centralize shared types in dedicated type filesImport specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Use LoadingContent component for async data with loading and error states
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*Form.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*Form.{ts,tsx}: Use React Hook Form with Zod for validation in form components
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧠 Learnings (28)
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields in the UI
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/components/ui/**/*.tsx : Shadcn UI components are located in `components/ui` directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Create corresponding server action implementation files using the naming convention `apps/web/utils/actions/NAME.ts` with 'use server' directive
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Add metadata with a meaningful action name using `.metadata({ name: "actionName" })` for Sentry instrumentation and monitoring
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text areas in forms, use the `Input` component with `type='text'`, `autosizeTextarea` prop, `rows`, `name`, `placeholder`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*.validation.{ts,tsx} : Use descriptive error messages in validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in form submissions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure to pass a message value when creating a built-in error
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Use descriptive error messages in Zod validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Handle form submission results using `result?.serverError` to show error toasts and `toastSuccess` to show success messages after server action completion
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission using React Hook Form and Zod resolver
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Use React Hook Form with Zod validation (`zodResolver`) for form handling, with form components using `register`, `handleSubmit`, and error handling from the hook
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for form validation
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use React Hook Form with Zod validation for form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for validation in form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/**/*.tsx : Use shadcn/ui components when available
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For text inputs, use the `Input` component with `registerProps` for form integration and error handling
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : For text areas, use the `Input` component with `type='text'`, `autosizeTextarea` prop set to true, and `registerProps` for form integration
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx (1)
5-57: Section wrapper abstraction looks clean and reusableThe
RuleSectionCardAPI and layout are clear and composable, and it cleanly separates header, errors, body, and footer actions. This should make the rule editor sections much easier to maintain.apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx (1)
142-158: Guard against undefinedactionTypewhen indexingactionInputs
actionTypeis read fromwatch("actions.${index}.type")and then used asactionInputs[actionType].fields. If, for any reason, the form state has an undefined or transiently emptytype, this will throw at runtime.Consider providing a safe fallback and optional chaining:
- const actionType = watch(`actions.${index}.type`); - const fields = actionInputs[actionType].fields; + const actionType = watch(`actions.${index}.type`) ?? ActionType.LABEL; + const fields = actionInputs[actionType]?.fields ?? [];This keeps the UI resilient even if the form state is temporarily incomplete.
Also applies to: 266-268, 269-271
⛔ Skipped due to learnings
Learnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use optional chaining where undefined values aren't allowedLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure label elements have text content and are associated with an inputLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ui-components.mdc:0-0 Timestamp: 2025-11-25T14:40:13.649Z Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` propsLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure typeof expressions are compared to valid valuesLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{js,jsx,ts,tsx} : Use valid ARIA state and property valuesLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: apps/web/CLAUDE.md:0-0 Timestamp: 2025-11-25T14:36:18.416Z Learning: Applies to apps/web/utils/actions/**/*.ts : Use `next-safe-action` with `actionClient` for server actions with Zod schema validationLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{jsx,tsx} : Don't use Array index in keysLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ui-components.mdc:0-0 Timestamp: 2025-11-25T14:40:13.649Z Learning: Applies to **/*.{tsx,ts,jsx,js} : For text areas in forms, use the `Input` component with `type='text'`, `autosizeTextarea` prop, `rows`, `name`, `placeholder`, `registerProps` from react-hook-form, and `error` propsLearnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-11-25T14:42:08.869Z Learning: Applies to **/*.{js,jsx,ts,tsx} : Use `at()` instead of integer index access
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
350-360: Global error summary can render blank items and duplicate/undefined keys
Object.values(errors).map((error) => (… key={error.message} …))assumes every entry is a flatFieldErrorwith amessage. In practice,errorscan contain nested objects/arrays, so you can end up with:
<li>items with no visible text (error.messageundefined)- Duplicate or
undefinedReact keysConsider mapping to messages, filtering falsy, and keying by index + message, in line with the prior Macroscope suggestion:
- description={ - <ul className="list-disc"> - {Object.values(errors).map((error) => ( - <li key={error.message}>{error.message}</li> - ))} - </ul> - } + description={ + <ul className="list-disc"> + {Object.values(errors) + .map((error: any) => error?.message) + .filter((msg): msg is string => !!msg) + .map((msg, index) => ( + <li key={`${index}-${msg}`}>{msg}</li> + ))} + </ul> + }
🧹 Nitpick comments (6)
apps/web/components/LoadingContent.tsx (2)
36-36: Remove redundant type assertion.The
errorparameter is already typed asLoadingContentProps["error"], which includesstatus?: number. The type assertion is unnecessary.- const status = (error as { status?: number })?.status; + const status = error?.status;
8-8: Consider consolidating the error type structure.The error type has a complex structure with two paths for error messages (
info?.erroranderror) plus the newstatusfield. This works but could be simplified for better maintainability.Consider defining a dedicated error type:
interface LoadingError { message: string; status?: number; info?: { error: string }; } // Then use in LoadingContentProps: error?: LoadingError;apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
191-195: Avoidconsole.errorin submit handlers; rely on toasts / logging utilitiesThe
console.error(res);calls in the update/create branches conflict with the “Don’t use console” guideline and can leak noisy output in production. You’re already surfacing failures viatoastError, so logging here is optional; if you still want logs, consider routing through a centralized logger instead ofconsole.- if (res?.serverError) { - console.error(res); - toastError({ description: res.serverError }); + if (res?.serverError) { + toastError({ description: res.serverError });And similarly for the create branch.
Also applies to: 222-224
386-425: New When/Then section cards and Advanced Settings dialog are a nice modularization (small nits only)The refactor to
RuleSectionCard+ConditionSteps/ActionSteps, plus the Advanced Settings dialog (runOnThreads, digest, LearnedPatterns, delete flow, cancel/save buttons), reads clean and keeps concerns separated while still wiring correctly into the form state and validation. This aligns well with the form-handling and UI guidelines (modular steps, inline errors, shadcn components). Two tiny nits you can consider:
- You can omit
headerActions={undefined}/footerActions={undefined}props onRuleSectionCardto trim noise;undefinedis the default.- The inline
onClickasync handler for the delete button is somewhat long; extracting it into auseCallback(e.g.handleDeleteRule) would slightly improve readability and testability, but it’s not required.No functional changes required here; these are optional cleanups.
Also applies to: 427-466, 468-571, 576-599, 604-619
apps/web/providers/SWRProvider.tsx (2)
141-150: Avoidconsole.login SWRonErrorand wire this into central error reportingThe new
getDevOnlySWRConfigintegration looks good, butonErrorstill logs directly to the console, which conflicts with the repo guideline to avoidconsole. Given you already havecaptureExceptionwired above, consider routing SWR errors through it instead (and, if you still want console output, gating that behindNODE_ENV === "development"):- // TODO: Send to Sentry - onError: (error) => console.log("SWR error:", error), + // TODO: Adjust tagging/metadata as needed, but avoid raw console logging. + onError: (error, key) => { + captureException(error, { + extra: { + source: "swr-onError", + key, + }, + }); + },This keeps error handling consistent with the rest of the file and removes direct console usage.
158-182: Add an explicit retry limit to prevent unbounded revalidation in developmentThe concern about unbounded retries is valid—the custom
onErrorRetryhandler has no cap and will retry indefinitely. However, the idiomatic approach in SWR 2.3.7 is to seterrorRetryCountin the config rather than checkingretryCountinside the callback:return { keepPreviousData: true, + errorRetryCount: 5, onErrorRetry: ( error: Error & { status?: number }, _key: string, _config: unknown, revalidate: (opts: { retryCount: number }) => void, { retryCount }: { retryCount: number }, ) => { // Retry 404s quickly (likely HMR transient errors) if (error.status === 404) { setTimeout(() => revalidate({ retryCount }), 500); return; } // Don't retry on other client errors (4xx) if (error.status && error.status >= 400 && error.status < 500) return; // Default exponential backoff for server errors setTimeout(() => revalidate({ retryCount }), 5000 * 2 ** retryCount); }, };This lets SWR handle the retry limit automatically rather than relying on manual checks. You could optionally type the function as
(): Partial<SWRConfiguration>for editor support.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(7 hunks)apps/web/components/LoadingContent.tsx(2 hunks)apps/web/providers/SWRProvider.tsx(3 hunks)version.txt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- version.txt
🧰 Additional context used
📓 Path-based instructions (16)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use@/path aliases for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Follow consistent naming conventions using PascalCase for components
Centralize shared types in dedicated type filesImport specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Use LoadingContent component for async data with loading and error states
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/components/**/*.tsx: Use React Hook Form with Zod validation for form components
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in form submissionsUse
LoadingContentcomponent to consistently handle loading and error states, passingloading,error, andchildrenpropsUse PascalCase for component file names (e.g.,
components/Button.tsx)
Files:
apps/web/components/LoadingContent.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/LoadingContent.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/components/LoadingContent.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*Form.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*Form.{ts,tsx}: Use React Hook Form with Zod for validation in form components
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧠 Learnings (42)
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*.tsx : Use `LoadingContent` component to consistently handle loading and error states, passing `loading`, `error`, and `children` props
Applied to files:
apps/web/components/LoadingContent.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/**/*.tsx : Use LoadingContent component for async data with loading and error states
Applied to files:
apps/web/components/LoadingContent.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use the `LoadingContent` component to handle loading states
Applied to files:
apps/web/components/LoadingContent.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : Use the `LoadingContent` component to handle loading states instead of manual loading state management
Applied to files:
apps/web/components/LoadingContent.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/components/LoadingContent.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : If we're in a deeply nested component we will use `swr` to fetch via API
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For API get requests to server, use the `swr` package with `useSWR` hook
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : For API GET requests to server, use the `swr` package
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: For mutating data, use Next.js server actions instead of SWR
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For API GET requests to server, use the `swr` package with hooks like `useSWR` to fetch data
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/hooks/use*.ts : Use SWR for client-side data fetching with typed responses from GET API routes
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*{.server,.action}.{ts,tsx} : If in a server file, you can fetch data inline instead of using SWR
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/hooks/use*.ts : Use SWR hooks for client-side data fetching, with hooks stored in `apps/web/hooks/use*.ts` that return typed responses from GET API routes
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Use `swr` for data fetching in deeply nested components
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:37:35.343Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:35.343Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching in custom hooks, prefer using `useSWR` and wrap it to handle API endpoint URL, returning data, loading state, error state, and potentially the `mutate` function
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields in the UI
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*.validation.{ts,tsx} : Use descriptive error messages in validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in form submissions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure to pass a message value when creating a built-in error
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Use descriptive error messages in Zod validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Handle form submission results using `result?.serverError` to show error toasts and `toastSuccess` to show success messages after server action completion
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission using React Hook Form and Zod resolver
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Use React Hook Form with Zod validation (`zodResolver`) for form handling, with form components using `register`, `handleSubmit`, and error handling from the hook
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for form validation
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use React Hook Form with Zod validation for form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for validation in form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text areas in forms, use the `Input` component with `type='text'`, `autosizeTextarea` prop, `rows`, `name`, `placeholder`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/components/ui/**/*.tsx : Shadcn UI components are located in `components/ui` directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/**/*.tsx : Use shadcn/ui components when available
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For text inputs, use the `Input` component with `registerProps` for form integration and error handling
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : For text areas, use the `Input` component with `type='text'`, `autosizeTextarea` prop set to true, and `registerProps` for form integration
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧬 Code graph analysis (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (7)
apps/web/utils/action-display.tsx (1)
getActionIcon(86-115)apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx (1)
RuleSectionCard(5-58)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(16-22)apps/web/utils/rule/consts.ts (1)
getRuleConfig(112-116)apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx (1)
ConditionSteps(121-475)apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx (1)
ActionSteps(46-107)apps/web/components/ui/form.tsx (1)
Form(172-172)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: test
🔇 Additional comments (3)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
5-6: Form + React Hook Form + Zod integration looks solid
useFormwithzodResolver(createRuleBody)plus the shadcn<Form>wrapper andhandleSubmit(onSubmit)wiring is clean and matches the React Hook Form + Zod guidelines for*Formcomponents. No changes needed here.Also applies to: 47-48, 96-120, 122-130, 348-349
140-145: Action field array + icon-backed action type options are well-structuredThe new
useFieldArrayforactionsfeeding intoActionSteps, and the enrichedtypeOptionsincludingicon: React.ElementTypepopulated viagetActionIcon(ActionType.…), give a clear, extensible shape for action configuration and UI. This should make future action types and icon changes straightforward without touching the form logic.Also applies to: 273-336, 449-465
apps/web/providers/SWRProvider.tsx (1)
97-106: SWRContext export is clean and correctly typedExporting
SWRContextwith the existingContextinterface anddefaultContextValuekeeps typing tight and enables reuse in consumers/tests without changing runtime behavior. No changes needed here.
| // In development, ignore 404 errors (likely transient HMR errors) | ||
| function shouldIgnoreError(error: LoadingContentProps["error"]): boolean { | ||
| if (process.env.NODE_ENV !== "development") return false; | ||
| const status = (error as { status?: number })?.status; | ||
| return status === 404; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, read the LoadingContent.tsx file to understand full context
cat -n apps/web/components/LoadingContent.tsxRepository: elie222/inbox-zero
Length of output: 1480
🏁 Script executed:
#!/bin/bash
# Search for LoadingContent usage to understand what errors are passed to it
rg -n 'LoadingContent' --type=tsx -g '!LoadingContent.tsx' -A 5 | head -100Repository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Check error handling patterns - look for how errors are passed to LoadingContent
rg -n 'error=' --type=tsx -A 2 -B 2 | grep -A 2 -B 2 LoadingContent | head -50Repository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Search for LoadingContent usage patterns to understand error handling
rg 'LoadingContent' -A 3 -B 1 apps/web/components apps/web/app | head -150Repository: elie222/inbox-zero
Length of output: 10625
🏁 Script executed:
#!/bin/bash
# Search for how errors with status codes are created/passed
rg 'status.*404|404.*status' -B 3 -A 3 apps/webRepository: elie222/inbox-zero
Length of output: 10644
🏁 Script executed:
#!/bin/bash
# Look for SWR or API error handling that might create 404 errors
rg 'useSWR|fetch|\.catch' -A 2 apps/web/components/LoadingContent.tsx apps/web/appRepository: elie222/inbox-zero
Length of output: 50374
🏁 Script executed:
#!/bin/bash
# Read SWRProvider to understand 404 handling
head -100 apps/web/providers/SWRProvider.tsxRepository: elie222/inbox-zero
Length of output: 2509
🏁 Script executed:
#!/bin/bash
# Search for legitimate 404 responses from API routes
rg 'status.*404|status: 404' apps/web/app/api -B 3 -A 1 | head -80Repository: elie222/inbox-zero
Length of output: 3449
🏁 Script executed:
#!/bin/bash
# Check if there are actual component implementations that rely on 404 errors
rg 'error.*404|404.*error' --type-add 'tsx:!{node_modules,dist,build}' apps/web/components -A 2 -B 2Repository: elie222/inbox-zero
Length of output: 461
Consider restricting 404 error handling to avoid masking legitimate "resource not found" responses.
The current implementation ignores all 404 errors in development mode, but the codebase legitimately returns 404 for actual resource-not-found scenarios (e.g., missing schedule, email account, user). While SWRProvider retries 404s quickly for transient HMR errors, LoadingContent will still suppress the error after retries exhaust, causing the component to display the loading state indefinitely whenever a real API endpoint returns 404.
To distinguish HMR-related 404s from actual API errors, consider:
- Checking the request path or error source before ignoring
- Adding a custom error code or header flag to HMR-specific errors
- Limiting the ignore logic to specific routes known to have HMR issues
🤖 Prompt for AI Agents
In apps/web/components/LoadingContent.tsx around lines 33-38, the helper
currently silences all 404 errors in development which can hide legitimate API
"not found" responses; change the logic to only ignore 404s that originate from
known HMR/static asset paths or an explicit HMR marker. Specifically, detect the
failing request URL (e.g., error.request?.url or error.config?.url) or a custom
error property and return true only when NODE_ENV is development, status ===
404, and the URL matches HMR/static patterns (like /_next/webpack-hmr,
/_next/static/, or other known dev-only routes) or when a dedicated isHMR
flag/header is present; otherwise return false so real API 404s (e.g., routes
under /api/, /schedules, /emails, /users) are not suppressed.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx (1)
71-79: Add explicittype="button"to the dropdown trigger.Same issue as previously flagged - this button lacks
type="button"and could trigger form submission.<Button size="icon" variant="ghost" className="size-8 mt-1" aria-label="More options" + type="button" >
🧹 Nitpick comments (5)
apps/web/providers/SWRProvider.tsx (1)
171-195: AdderrorRetryCountlimit to prevent unbounded exponential backoff in development.SWR defaults to unlimited retries (not limited as previously assumed), so the exponential backoff here grows without bounds. At retryCount=10, delays reach ~85 minutes. Consider adding
errorRetryCount: 3or similar to the dev config to cap retries, or document if unlimited retries are intentional. Since this is dev-only, the practical impact is limited, but it could cause frustrating long waits during development if server errors persist.apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx (1)
743-773: Consider documenting the-1sentinel value.The
convertToMinutesfunction returns-1for invalid inputs. While this is handled consistently, adding a brief comment or using a named constant would improve clarity for future maintainers.+// Sentinel value indicating an invalid delay (will be caught by validation) +const INVALID_DELAY = -1; + // user-friendly UI format to minutes function convertToMinutes(value: string, unit: string) { const numValue = Number.parseInt(value, 10); - if (Number.isNaN(numValue) || numValue <= 0) return -1; + if (Number.isNaN(numValue) || numValue <= 0) return INVALID_DELAY;apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (3)
350-363: Consider filtering errors with valid messages.The error display may render items with
undefinedkeys if nested error objects don't have a directmessageproperty. This relates to a past review comment. Consider filtering to only display errors with valid messages.<ul className="list-disc"> - {Object.values(errors).map((error) => ( - <li key={error.message}>{error.message}</li> - ))} + {Object.values(errors) + .filter((error): error is { message: string } => + typeof error?.message === 'string' + ) + .map((error, i) => ( + <li key={`${i}-${error.message}`}>{error.message}</li> + ))} </ul>
555-556: Add error parameter to catch block.Per coding guidelines, when creating built-in errors, a message value should be passed. The catch block here has no error parameter, but it does show a toast - consider logging the error for debugging purposes.
- } catch { + } catch (error) { toastError({ description: "Failed to delete rule." }); + console.error("Delete rule error:", error); } finally {
608-613: Consider extractingallowMultipleConditionsto a shared utility.This function is duplicated in
ConditionSteps.tsx. While small, extracting it to a shared location (e.g.,@/utils/rule/consts.tswheregetRuleConfigalready exists) would improve maintainability.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(7 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx(1 hunks)apps/web/providers/SWRProvider.tsx(5 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use@/path aliases for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Follow consistent naming conventions using PascalCase for components
Centralize shared types in dedicated type filesImport specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Use LoadingContent component for async data with loading and error states
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/providers/SWRProvider.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
**/*Form.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*Form.{ts,tsx}: Use React Hook Form with Zod for validation in form components
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧠 Learnings (53)
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : System prompts must define the LLM's role and task specifications
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Always include a `type` attribute for button elements
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use valid, non-abstract ARIA roles for elements with ARIA roles
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2024-08-23T11:37:26.779Z
Learnt from: aryanprince
Repo: elie222/inbox-zero PR: 210
File: apps/web/app/(app)/stats/NewsletterModal.tsx:2-4
Timestamp: 2024-08-23T11:37:26.779Z
Learning: `MoreDropdown` is a React component and `useUnsubscribeButton` is a custom React hook, and they should not be imported using `import type`.
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure label elements have text content and are associated with an input
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use valid ARIA state and property values
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure ARIA properties are valid for the element's supported roles
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't assign non-interactive ARIA roles to interactive HTML elements
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't set `aria-hidden="true"` on focusable elements
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/components/ui/**/*.tsx : Shadcn UI components are located in `components/ui` directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use Shadcn UI and Tailwind for components and styling
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/**/*.tsx : Use shadcn/ui components when available
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : Use Shadcn UI and Tailwind for components and styling
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use duplicate conditions in if-else-if chains
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : If we're in a deeply nested component we will use `swr` to fetch via API
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/hooks/use*.ts : Use SWR for client-side data fetching with typed responses from GET API routes
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For API get requests to server, use the `swr` package with `useSWR` hook
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For API GET requests to server, use the `swr` package with hooks like `useSWR` to fetch data
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : For API GET requests to server, use the `swr` package
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/hooks/use*.ts : Use SWR hooks for client-side data fetching, with hooks stored in `apps/web/hooks/use*.ts` that return typed responses from GET API routes
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching, prefer using `useSWR` and follow the data-fetching guidelines
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: For mutating data, use Next.js server actions instead of SWR
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Use `swr` for data fetching in deeply nested components
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*{.server,.action}.{ts,tsx} : If in a server file, you can fetch data inline instead of using SWR
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:37:35.343Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:35.343Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching in custom hooks, prefer using `useSWR` and wrap it to handle API endpoint URL, returning data, loading state, error state, and potentially the `mutate` function
Applied to files:
apps/web/providers/SWRProvider.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields in the UI
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Show validation errors inline next to form fields
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*.validation.{ts,tsx} : Use descriptive error messages in validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in form submissions
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure to pass a message value when creating a built-in error
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Use descriptive error messages in Zod validation schemas
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Handle form submission results using `result?.serverError` to show error toasts and `toastSuccess` to show success messages after server action completion
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission using React Hook Form and Zod resolver
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Use React Hook Form with Zod validation (`zodResolver`) for form handling, with form components using `register`, `handleSubmit`, and error handling from the hook
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for form validation
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/components/**/*.tsx : Use React Hook Form with Zod validation for form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for validation in form components
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text areas in forms, use the `Input` component with `type='text'`, `autosizeTextarea` prop, `rows`, `name`, `placeholder`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For text inputs, use the `Input` component with `registerProps` for form integration and error handling
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : For text areas, use the `Input` component with `type='text'`, `autosizeTextarea` prop set to true, and `registerProps` for form integration
Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧬 Code graph analysis (2)
apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx (2)
apps/web/components/new-landing/common/Card.tsx (1)
Card(67-105)apps/web/components/Typography.tsx (1)
TypographyH3(125-125)
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx (5)
apps/web/utils/actions/rule.validation.ts (2)
ZodCondition(62-62)CreateRuleBody(194-194)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(16-22)apps/web/app/(app)/[emailAccountId]/assistant/RuleSteps.tsx (1)
RuleSteps(7-40)apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx (1)
RuleStep(185-237)apps/web/utils/rule/consts.ts (1)
getRuleConfig(112-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (15)
apps/web/providers/SWRProvider.tsx (4)
38-51: LGTM!Good defensive handling for non-JSON error responses. The try/catch gracefully handles HMR 404s in development while still logging unexpected failures in production for debugging.
80-88: LGTM!The error shape with
infoandstatusproperties aligns well with SWR's documented error handling pattern, providing consistent error structure for downstream consumers.
118-118: LGTM!Exporting
SWRContextenables direct context consumption when needed, which is useful for advanced use cases outside the default provider hooks.
162-162: LGTM!Clean integration that spreads dev-only config without affecting production behavior.
apps/web/app/(app)/[emailAccountId]/assistant/RuleSectionCard.tsx (1)
1-37: LGTM!Clean, well-structured component following shadcn/ui patterns. Good use of conditional rendering for optional
errorsandchildrenblocks, and proper dark mode support via Tailwind classes.apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx (1)
185-237: LGTM!The main
RuleStepcomponent is well-structured with clean composition of layout primitives and action buttons. Props are properly typed with appropriate optional handlers.apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx (2)
46-107: LGTM!Well-structured component with proper typing integration with
CreateRuleBody. Clean mapping of action fields with appropriate keys.
617-658: LGTM!Good helper dialog for showing variable examples. The template examples are clear and instructive for users.
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx (5)
44-61: LGTM!Clean conversion logic between backend
ZodConditionand UI-level condition types. The comment explainingbodymapping tosubjectin UI is helpful.
165-176: LGTM!The
useEffectcorrectly initializes the first condition to prompt type only when empty, preserving existing static conditions. Good defensive logic.
256-271: LGTM!The duplicate detection logic correctly filters out
undefinedvalues before creating the Set, addressing the previously identified bug where multiple unselected conditions would incorrectly trigger the duplicate error.
277-300: LGTM!Good handling of AI prompt positioning - automatically moving it to the first position while preserving existing condition data. The logic correctly handles both configured and unconfigured conditions during reordering.
449-534: LGTM!Clean per-type field rendering with proper
registerPropsintegration, inline error handling, and helpful tooltips. Good use ofTextareaAutosizefor the prompt field.apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
273-336: LGTM!Clean integration of action icons via
getActionIcon. The memoization dependencies are correct, and all action types are properly mapped.
386-460: LGTM!Clean integration of modular components. The
RuleSectionCardwrapper provides consistent styling and error display, whileConditionStepsandActionStepshandle their respective domains well. Props are properly threaded through.
| <Button | ||
| size="xs" | ||
| variant="ghost" | ||
| className="flex items-center gap-1 text-xs text-muted-foreground" | ||
| onClick={() => setExpandedFields(!expandedFields)} | ||
| > | ||
| {expandedFields ? ( | ||
| <> | ||
| <ChevronDownIcon className="h-3.5 w-3.5" /> | ||
| Hide extra fields | ||
| </> | ||
| ) : ( | ||
| <> | ||
| <ChevronRightIcon className="h-3.5 w-3.5" /> | ||
| Show all fields | ||
| </> | ||
| )} | ||
| </Button> |
There was a problem hiding this comment.
Add type="button" to the expand/collapse button.
This button is inside a form context and toggles UI state. Without type="button", it defaults to submit behavior. As per coding guidelines, always include a type attribute for button elements.
<Button
size="xs"
variant="ghost"
className="flex items-center gap-1 text-xs text-muted-foreground"
onClick={() => setExpandedFields(!expandedFields)}
+ type="button"
>📝 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.
| <Button | |
| size="xs" | |
| variant="ghost" | |
| className="flex items-center gap-1 text-xs text-muted-foreground" | |
| onClick={() => setExpandedFields(!expandedFields)} | |
| > | |
| {expandedFields ? ( | |
| <> | |
| <ChevronDownIcon className="h-3.5 w-3.5" /> | |
| Hide extra fields | |
| </> | |
| ) : ( | |
| <> | |
| <ChevronRightIcon className="h-3.5 w-3.5" /> | |
| Show all fields | |
| </> | |
| )} | |
| </Button> | |
| <Button | |
| size="xs" | |
| variant="ghost" | |
| className="flex items-center gap-1 text-xs text-muted-foreground" | |
| onClick={() => setExpandedFields(!expandedFields)} | |
| type="button" | |
| > | |
| {expandedFields ? ( | |
| <> | |
| <ChevronDownIcon className="h-3.5 w-3.5" /> | |
| Hide extra fields | |
| </> | |
| ) : ( | |
| <> | |
| <ChevronRightIcon className="h-3.5 w-3.5" /> | |
| Show all fields | |
| </> | |
| )} | |
| </Button> |
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx around lines
483 to 500, the expand/collapse Button is inside a form and lacks an explicit
type, causing it to default to submit behavior; update the Button props to
include type="button" so it only toggles UI state (setExpandedFields) and does
not submit the form.
| {onCancel && ( | ||
| <Button variant="outline" size="sm" onClick={onCancel}> | ||
| Cancel | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
Add type="button" to the Cancel button.
The Cancel button should not submit the form. Without an explicit type="button", it could inadvertently submit in some browsers.
{onCancel && (
- <Button variant="outline" size="sm" onClick={onCancel}>
+ <Button variant="outline" size="sm" onClick={onCancel} type="button">
Cancel
</Button>
)}📝 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.
| {onCancel && ( | |
| <Button variant="outline" size="sm" onClick={onCancel}> | |
| Cancel | |
| </Button> | |
| )} | |
| {onCancel && ( | |
| <Button variant="outline" size="sm" onClick={onCancel} type="button"> | |
| Cancel | |
| </Button> | |
| )} |
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx around lines 571
to 575, the Cancel button is missing an explicit type and may submit the form in
some browsers; update the Button element to include type="button" so clicking
Cancel does not trigger form submission and keeps its current onClick handler
intact.
| return ( | ||
| <Button | ||
| size="icon" | ||
| variant="ghost" | ||
| className="size-8 mt-1" | ||
| aria-label={ariaLabel} | ||
| onClick={onClick} | ||
| > | ||
| <TrashIcon className="size-4 text-muted-foreground" /> | ||
| </Button> | ||
| ); |
There was a problem hiding this comment.
Add explicit type="button" to prevent form submission.
This DeleteButton is rendered inside RuleForm which wraps content in a <form> element. Without an explicit type="button", clicking could inadvertently submit the form. As per coding guidelines, buttons should always include a type attribute.
<Button
size="icon"
variant="ghost"
className="size-8 mt-1"
aria-label={ariaLabel}
onClick={onClick}
+ type="button"
>📝 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.
| return ( | |
| <Button | |
| size="icon" | |
| variant="ghost" | |
| className="size-8 mt-1" | |
| aria-label={ariaLabel} | |
| onClick={onClick} | |
| > | |
| <TrashIcon className="size-4 text-muted-foreground" /> | |
| </Button> | |
| ); | |
| return ( | |
| <Button | |
| size="icon" | |
| variant="ghost" | |
| className="size-8 mt-1" | |
| aria-label={ariaLabel} | |
| onClick={onClick} | |
| type="button" | |
| > | |
| <TrashIcon className="size-4 text-muted-foreground" /> | |
| </Button> | |
| ); |
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx around lines 24 to
34, the DeleteButton lacks an explicit type which can cause it to submit the
parent form; add type="button" to the Button element (e.g., pass a type prop) so
the button does not trigger form submission, keeping all other props and
behavior unchanged.
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
apps/web/utils/condition.ts (1)
149-171:flattenConditionswill overwrite fields when merging per-field STATIC conditions.The new
getConditionsfunction creates separate STATIC conditions per field (e.g.,{from: "test", to: null}and{from: null, to: "user"}). However,flattenConditionsunconditionally assigns all fields at lines 158-161, causing later conditions to overwrite earlier values withnull.Example:
[{from: "a@test.com", to: null}, {from: null, to: "b@test.com"}]results in{from: null, to: "b@test.com"}— losing thefromvalue.Apply this diff to merge only non-null values:
case ConditionType.STATIC: - acc.to = condition.to; - acc.from = condition.from; - acc.subject = condition.subject; - acc.body = condition.body; + if (condition.to !== null) acc.to = condition.to; + if (condition.from !== null) acc.from = condition.from; + if (condition.subject !== null) acc.subject = condition.subject; + if (condition.body !== null) acc.body = condition.body; break;apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx (2)
24-34: Add explicittype="button"to prevent form submission.This button is rendered inside forms via
RuleForm. Without an explicittype="button", it defaults totype="submit"which could inadvertently submit the form. As per coding guidelines, buttons should always include atypeattribute.<Button size="icon" variant="ghost" className="size-8 mt-1" aria-label={ariaLabel} onClick={onClick} + type="button" >
69-79: Add explicittype="button"to dropdown trigger.Same issue as
DeleteButton— this button could trigger form submission without an explicit type.<Button size="icon" variant="ghost" className="size-8 mt-1" aria-label="More options" + type="button" >apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
579-583: Addtype="button"to the Cancel button.Without an explicit
type="button", the Cancel button may inadvertently submit the form in some browsers. As per coding guidelines, always include atypeattribute for button elements.{onCancel && ( - <Button variant="outline" size="sm" onClick={onCancel}> + <Button variant="outline" size="sm" onClick={onCancel} type="button"> Cancel </Button> )}
🧹 Nitpick comments (7)
apps/web/app/(app)/[emailAccountId]/setup/StatsCardGrid.tsx (2)
86-86: Consider using a stable identifier for the key prop.Using array index as the key is acceptable here since the items array is static, but if the items ever become dynamic (reorderable, filterable, etc.), this could cause React rendering issues. Consider using
item.titleas a stable identifier instead:- <div key={index} className="flex-1 p-6"> + <div key={item.title} className="flex-1 p-6">
73-109: Clean responsive layout with good accessibility.The rendering logic follows best practices:
- Mobile-first responsive design with proper breakpoints
- Dark mode support through variant classes
- Tooltip accessibility handled by shadcn/ui components
- Semantic HTML structure
Optional: Consider using the
cn()utility (from@/utils/cnor similar) instead of template literals for className construction to benefit from better type safety and class merging:import { cn } from "@/utils/cn"; // Instead of: className={`size-10 mb-4 flex items-center justify-center rounded-lg ${variant.iconBg}`} // Use: className={cn("size-10 mb-4 flex items-center justify-center rounded-lg", variant.iconBg)}apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (2)
411-412: Improve left-alignment withitems-startfor better wrapping.When action badges wrap to multiple lines,
items-startensures consistent left alignment across rows.- <div className="flex gap-1 sm:gap-2 flex-wrap min-w-0 justify-start"> + <div className="flex gap-1 sm:gap-2 flex-wrap min-w-0 justify-start items-start">
463-476: Empty tooltip content when no conditions exist.When a rule has neither AI nor static conditions, the tooltip will show an empty string from
conditionsToString(rule), and the icons div will be empty. Consider hiding the component entirely in this case.function StaticConditionsIcon({ rule }: { rule: RulesResponse[number] }) { + const hasConditions = hasAiConditions(rule) || hasStaticConditions(rule); + if (!hasConditions) return null; + return ( <Tooltip content={conditionsToString(rule)}> <div className="flex items-center gap-2"> {hasAiConditions(rule) && ( <BotIcon className="size-4 text-muted-foreground" /> )} {hasStaticConditions(rule) && ( <FilterIcon className="size-4 text-muted-foreground" /> )} </div> </Tooltip> ); }apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx (2)
302-302: Controlled Select with potentially undefined value.Setting
value={uiType || undefined}whenuiTypeis alreadyundefinedis redundant. The|| undefineddoesn't change behavior sinceundefined || undefinedis stillundefined. This is minor but could be simplified.- value={uiType || undefined} + value={uiType}
420-426: TextareaAutosize styling uses direct border classes instead of design tokens.The textarea uses hardcoded
border-borderandfocus:border-blackwhich may not align with dark mode. Consider using Tailwind's ring utilities for consistency with shadcn/ui patterns.<TextareaAutosize - className="block w-full flex-1 whitespace-pre-wrap rounded-md border border-border bg-background shadow-sm focus:border-black focus:ring-black sm:text-sm" + className="block w-full flex-1 whitespace-pre-wrap rounded-md border border-input bg-background px-3 py-2 shadow-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 sm:text-sm" minRows={3} rows={3} {...register(`conditions.${index}.instructions`)} placeholder="e.g. Newsletters, regular content from publications, blogs, or services I've subscribed to" />apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
275-279: formErrors may miss nested validation errors.The filter
Boolean(error.message)only catches top-level errors with a directmessageproperty. Nested errors from arrays likeconditionsoractionshave different structures. Consider also checking forroot.messageon nested errors if you want to surface them here.const formErrors = useMemo(() => { - return Object.values(formState.errors) - .filter((error): error is { message: string } => Boolean(error.message)) - .map((error) => error.message); + const messages: string[] = []; + for (const error of Object.values(formState.errors)) { + if (error && typeof error === 'object') { + if ('message' in error && typeof error.message === 'string') { + messages.push(error.message); + } + if ('root' in error && error.root && typeof error.root === 'object' && 'message' in error.root && typeof error.root.message === 'string') { + messages.push(error.root.message); + } + } + } + return messages; }, [formState]);
| // Convert backend condition to UI type | ||
| function getUIConditionType( | ||
| condition: ZodCondition, | ||
| ): UIConditionType | undefined { | ||
| if (condition.type === ConditionType.AI) { | ||
| return "prompt"; | ||
| } | ||
| // For STATIC conditions, determine which field is populated | ||
| // With the new structure, each STATIC condition should only have one field | ||
| // We set the active field to "" (empty string) and others to null | ||
| // So we check which field is not null to determine the UI type | ||
| if (condition.from !== null) return "from"; | ||
| if (condition.to !== null) return "to"; | ||
| if (condition.subject !== null) return "subject"; | ||
| if (condition.body !== null) return "subject"; // body maps to subject in UI | ||
| // Return undefined if no field is populated (new/unselected condition) | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check references to body in the ConditionSteps file and condition utilities
rg -n "body" apps/web/app/\(app\)/\[emailAccountId\]/assistant/ConditionSteps.tsx -A 2 -B 2Repository: elie222/inbox-zero
Length of output: 861
🏁 Script executed:
# Search for UIConditionType definition to understand the type structure
rg -n "type UIConditionType|UIConditionType =" apps/web --type ts --type tsxRepository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
# Check condition utilities and types
rg -n "ConditionType|ZodCondition" apps/web/utils/condition.ts -A 3 -B 1 | head -100Repository: elie222/inbox-zero
Length of output: 2766
🏁 Script executed:
# Look for any form or UI components handling body conditions
rg -n "body.*condition|condition.*body" apps/web/app/\(app\)/\[emailAccountId\]/assistant/ --type tsx -B 2 -A 2Repository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
# Find UIConditionType definition
rg "UIConditionType" apps/web/app/\(app\)/\[emailAccountId\]/assistant/ConditionSteps.tsxRepository: elie222/inbox-zero
Length of output: 1069
🏁 Script executed:
# Check the rule validation schema to understand body field in ZodCondition
rg -n "body" apps/web/utils/actions/rule.validation.ts -A 2 -B 2Repository: elie222/inbox-zero
Length of output: 518
🏁 Script executed:
# Look for any body-related condition form or display logic
rg -n "body" apps/web/app/\(app\)/\[emailAccountId\]/assistant/ -B 2 -A 2Repository: elie222/inbox-zero
Length of output: 6993
🏁 Script executed:
# Check if body is ever actually set/populated in conditions
rg -n "condition\.body\s*=" apps/web --type tsRepository: elie222/inbox-zero
Length of output: 44
The body-to-subject mapping is a bug that prevents body conditions from being properly edited in the UI.
The backend fully supports body as a separate condition field, and ResultDisplay.tsx displays body conditions with their own label (Body: "..."). However, UIConditionType only defines options for "from" | "to" | "subject" | "prompt" with no "body" option. Line 58 maps condition.body to "subject" as a workaround, which means body conditions cannot be properly created or edited through the UI—users selecting a body condition would instead edit it as a subject condition. To fix this, add "body" to the UIConditionType definition and update the condition selection UI to support it.
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/ConditionSteps.tsx around lines
44 to 61, the function maps backend STATIC condition.body to "subject" which
prevents body conditions from being edited correctly; add "body" to the
UIConditionType union/type definition, change this function to return "body"
when condition.body is populated (instead of "subject"), and update the
condition selection UI/control (and any related type guards or switch branches)
to include and render the "body" option so body conditions can be created and
edited independently from subject.
| {rule.id && ( | ||
| <Button | ||
| size="sm" | ||
| variant="outline" | ||
| Icon={TrashIcon} | ||
| loading={isDeleting} | ||
| disabled={isSubmitting} | ||
| onClick={async () => { | ||
| const yes = confirm( | ||
| "Are you sure you want to delete this rule?", | ||
| ); | ||
| if (yes) { | ||
| try { | ||
| setIsDeleting(true); | ||
| const result = await deleteRuleAction( | ||
| emailAccountId, | ||
| { | ||
| id: rule.id!, | ||
| }, | ||
| ); | ||
| if (result?.serverError) { | ||
| toastError({ | ||
| description: result.serverError, | ||
| }); | ||
| } else { | ||
| toastSuccess({ | ||
| description: "The rule has been deleted.", | ||
| }); | ||
|
|
||
| if (isDialog && onSuccess) { | ||
| onSuccess(); | ||
| } | ||
|
|
||
| router.push( | ||
| prefixPath( | ||
| emailAccountId, | ||
| "/automation?tab=rules", | ||
| ), | ||
| ); | ||
| } | ||
| } catch { | ||
| toastError({ description: "Failed to delete rule." }); | ||
| } finally { | ||
| setIsDeleting(false); | ||
| } | ||
| } | ||
| }} | ||
| > | ||
| Delete rule | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
Delete button inside Dialog should have type="button".
The delete button is inside a <form> and uses onClick with async logic. Without type="button", it could trigger form submission. Though the Button component may handle this, being explicit is safer.
<Button
size="sm"
variant="outline"
Icon={TrashIcon}
loading={isDeleting}
disabled={isSubmitting}
+ type="button"
onClick={async () => {🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx around lines 523
to 573, the Delete button rendered inside a form lacks an explicit type and may
trigger form submission when clicked; update the Button element to include
type="button" to prevent accidental form submit, leaving the existing onClick
async handler and disabled/loading props unchanged.
| import { Button } from "@/components/ui/button"; | ||
| import { | ||
| TrashIcon, | ||
| MoreHorizontalIcon, | ||
| ClockIcon, | ||
| SparklesIcon, | ||
| PenLineIcon, | ||
| } from "lucide-react"; | ||
| import { cn } from "@/utils"; | ||
| import { | ||
| DropdownMenu, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuTrigger, | ||
| } from "@/components/ui/dropdown-menu"; | ||
|
|
There was a problem hiding this comment.
Missing 'use client' directive.
This component uses onClick handlers (lines 30, 82, 88, 94, 100, 106, 112) which require client-side interactivity. Per coding guidelines, components with onClick must be client components.
Add the directive at the top of the file:
+'use client';
+
import { Button } from "@/components/ui/button";🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleStep.tsx around lines 1 to
16, this React component uses multiple onClick handlers (lines ~30, 82, 88, 94,
100, 106, 112) and therefore must be a client component; add the "use client"
directive as the very first line of the file (before any imports) so the file
runs on the client and supports interactivity, then verify linting and that no
server-only APIs are used in the component.
Add meeting briefs feature and upgrade automation rules UI, stats APIs, and LLM config including Ollama with new cron endpoint at 15 minutes and watch renewal every 6 hours
Introduce Meeting Briefs with settings, cron endpoints, email template, providers for Google/Microsoft calendars, and server actions; refactor Automation Rules UI (steps, actions, menus) and table; add stats APIs (by-period, response time) with controllers and OpenAPI; integrate new Chat open flow; add Outlook unread-preserving draft and provider thread queries; and enable Ollama model selection via env with tests and schema updates.
📍Where to Start
Start with Meeting Briefs flow in the API entrypoint
GET /api/meeting-briefsand controller pipeline via apps/web/app/api/meeting-briefs/route.ts, then review the processing workflow in apps/web/utils/meeting-briefs/process.ts.Macroscope summarized c1c2e2c.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Introduces a step-based rule builder with improved action UX, adds setup dashboard metrics (and API), streamlines the rules list, and adds a Home link.
RuleSectionCard,RuleStep,RuleStepsplusConditionStepsandActionStepsfor step-based editing inRuleForm.DelayInputControls), AI/manual label selection, and webhook label text.condition.tssplits/merges static conditions accordingly.StatsCardGridand displays inbox metrics (emails processed, rules executed, inbox coverage) on setup page.GET /api/user/setup-progressto return the above metrics.Combobox/LabelCombobox: support custom footer and improved UX.AddRuleDialog: right-aligned trigger button.Written by Cursor Bugbot for commit 096ea69. Configure here.