Skip to content

New onboarding flow#682

Merged
elie222 merged 41 commits intomainfrom
feat/persona-analysis
Aug 17, 2025
Merged

New onboarding flow#682
elie222 merged 41 commits intomainfrom
feat/persona-analysis

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Aug 14, 2025

Summary by CodeRabbit

  • New Features

    • Multi-step onboarding: persona-driven suggestions, role selection, feature picks, draft-replies, custom rules, extension install, and image previews.
  • Improvements

    • Redesigned category setup with per-category actions, refreshed icons/visuals, continue-driven flow, onboarding analytics and variant support.
  • Bug Fixes

    • Delete confirmation now displays the specific rule name.
  • Chores

    • Backend onboarding & persona endpoints/hooks, preferences API shape change, database migrations, config/path and dependency updates; version bump.

@vercel
Copy link

vercel bot commented Aug 14, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Aug 17, 2025 4:57pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Centralizes onboarding into a callback-driven seven-step flow, converts category/rule shapes to an array-based model, adds persona API/hooks and server actions, updates Prisma with personaAnalysis and role, refactors rule creation/validation and categoryConfig, and updates onboarding UI primitives and provider helpers.

Changes

Cohort / File(s) Summary
Onboarding Orchestrator
apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx, apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx
Add OnboardingContent to orchestrate seven callback-driven steps, analytics lifecycle, persona kick-off, and navigation via onNext callbacks.
Onboarding Steps (callback-driven)
apps/web/app/(app)/[emailAccountId]/onboarding/StepIntro.tsx, .../StepFeatures.tsx, .../StepWho.tsx, .../StepLabels.tsx, .../StepDraft.tsx, .../StepCustomRules.tsx, .../StepExtension.tsx, .../StepDigest.tsx, .../StepDigestV1.tsx
Convert step components to onNext-based callbacks, adjust layouts/images, wire server actions for role/features/draft, and remove href-based navigation.
Categories / Onboarding UIs
apps/web/app/(app)/[emailAccountId]/assistant/onboarding/CategoriesSetup.tsx, apps/web/app/(app)/[emailAccountId]/assistant/onboarding/page.tsx, apps/web/app/(app)/[emailAccountId]/assistant/onboarding/OnboardingCategories.tsx
Consolidate per-category defaults into a categories array, change form/data shape to CreateRulesOnboardingBody (array), introduce persona-driven suggested/basic UI, and update component props.
Rules Actions & Validation
apps/web/utils/actions/rule.ts, apps/web/utils/actions/rule.validation.ts, apps/web/utils/category-config.tsx, apps/web/utils/rule/consts.ts
Rewrite createRulesOnboardingAction to handle system and custom categories, change validation to z.array(categoryConfig), introduce CategoryConfig/CreateRulesOnboardingBody types, update categoryConfig shape and add SystemRule.
Persona API, Hook & Actions
apps/web/app/api/user/persona/route.ts, apps/web/hooks/usePersona.ts, apps/web/utils/actions/email-account.ts, apps/web/utils/ai/knowledge/persona.ts
Add /api/user/persona GET and usePersona SWR hook; add analyzePersonaAction and updateEmailAccountRoleAction server actions; tweak persona prompt wording.
Preferences API Shape
apps/web/app/api/user/categorization-preferences/route.ts
Change getUserPreferences to return a CreateRulesOnboardingBody array of seven entries (return [] when absent); adjust helper signatures.
UI Primitives
apps/web/app/(app)/[emailAccountId]/onboarding/IconCircle.tsx, apps/web/app/(app)/[emailAccountId]/onboarding/ContinueButton.tsx, apps/web/app/(app)/[emailAccountId]/onboarding/ImagePreview.tsx
Refactor IconCircle to CVA variants and typed props (Icon, colors); ContinueButton now accepts ButtonProps; add OnboardingImagePreview.
Onboarding Config & Flags
apps/web/app/(app)/[emailAccountId]/onboarding/config.ts, apps/web/hooks/useFeatureFlags.ts, apps/web/app/(landing)/welcome-redirect/page.tsx, apps/web/utils/config.ts
Add usersRolesInfo mapping, introduce useOnboardingVariant hook, add welcome-redirect page, and export WELCOME_PATH = "/welcome-redirect".
Analytics & Welcome Flow
apps/web/hooks/useAnalytics.ts, apps/web/app/(landing)/welcome/form.tsx
Add useOnboardingAnalytics (onStart/onNext/onComplete) and integrate analytics into welcome survey flow.
Onboarding Actions Module
apps/web/utils/actions/onboarding.ts, apps/web/utils/actions/onboarding.validation.ts, apps/web/utils/actions/user.ts
Add completedOnboardingAction, saveOnboardingAnswersAction, saveOnboardingFeaturesAction and schemas under utils/actions/onboarding; remove older onboarding actions from utils/actions/user.
Auth & Redirects
apps/web/app/(landing)/login/LoginForm.tsx, apps/web/app/(landing)/login/error/page.tsx, apps/web/app/(landing)/thank-you/page.tsx
Centralize post-login redirect to WELCOME_PATH; update error redirect and ThankYou continue href to /setup.
Cold Email & Provider
apps/web/utils/actions/cold-email.ts
testColdEmailAction now reads provider from action context (ctx.provider) rather than DB account.provider.
Provider Checks & Email Provider
apps/web/utils/email/provider-types.ts, apps/web/providers/EmailProvider.tsx, apps/web/utils/actions/whitelist.ts
Rename/swap provider-check helpers (isGoogleProvider / isMicrosoftProvider) and update callers to new predicates.
LLM Config
apps/web/utils/llms/config.ts
Update GEMINI_2_5_PRO_OPENROUTER value to "google/gemini-2.5-pro".
Assistant Minor
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx, apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
Interpolate rule name into delete confirmation prompt; reformat a sponsorship example in persona prompts.
Prisma Schema & Migrations
apps/web/prisma/schema.prisma, apps/web/prisma/migrations/...persona_analysis.../migration.sql, apps/web/prisma/migrations/...email_account_role.../migration.sql
Add EmailAccount.personaAnalysis (Json?) and EmailAccount.role (String?) with corresponding migration SQL files.
Package deps & Version
apps/web/package.json, version.txt
Bump react-hook-form and @hookform/resolvers, remove zod-to-json-schema; update version to v2.5.8.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant OnboardingContent
  participant Analytics
  participant PersonaAPI as /api/user/persona
  participant EmailActions as email-account actions

  User->>OnboardingContent: Open onboarding (step N)
  OnboardingContent->>Analytics: onStart()
  OnboardingContent->>PersonaAPI: GET /api/user/persona
  alt no personaAnalysis
    OnboardingContent->>EmailActions: analyzePersonaAction(emailAccountId)
    EmailActions-->>PersonaAPI: persist personaAnalysis
    PersonaAPI-->>OnboardingContent: updated data (mutate)
  end
  User->>OnboardingContent: Click Continue
  OnboardingContent->>Analytics: onNext(step+1)
  OnboardingContent-->>User: Advance via onNext callback
Loading
sequenceDiagram
  participant User
  participant CategoriesSetup
  participant StepLabels
  participant RuleAction as createRulesOnboardingAction
  participant Server

  User->>CategoriesSetup: Configure categories (categories[])
  User->>StepLabels: Submit categories
  StepLabels->>RuleAction: submit(categories[])
  RuleAction->>Server: create/update system & custom rules, update prompts
  Server-->>RuleAction: results
  RuleAction-->>StepLabels: done
  StepLabels-->>User: Advance via onNext
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Merge main staging #669 — Large onboarding refactor touching IconCircle, ContinueButton, persona hooks, and step callback migration; likely overlapping components and behavior.
  • Simple assistant onboarding #382 — Overlaps CategoriesSetup and createRulesOnboardingAction changes (category onboarding UI and rule creation/validation).
  • Add Outlook Cold Email support #603 — Related to cold-email/provider abstraction changes (testColdEmailAction now consumes provider from context).

Poem

I hopped through code to stitch new trails,
I nudged the steps and tended the rails.
Labels, personas, prompts in line,
Buttons call onNext — the flow’s divine.
A little rabbit cheer — hop onward, shine! 🐇✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 503993a and 5a17b81.

📒 Files selected for processing (1)
  • version.txt (1 hunks)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/persona-analysis

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

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

Status, Documentation and Community

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

@socket-security
Copy link

socket-security bot commented Aug 14, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​hookform/​resolvers@​5.0.1 ⏵ 5.2.199100100 +193 -1100

View full report

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🔭 Outside diff range comments (8)
apps/web/utils/ai/knowledge/persona.ts (3)

87-95: Prefer XML-like structure in the prompt and avoid duplicating the email address.

You already use tags for emails; wrap the task directive similarly and avoid repeating the address in free text. This tightens structure and reduces prompt ambiguity.

Apply this diff to re-structure the changed prompt segment:

-  const prompt = `The user's email address is: ${emailAccount.email}
-
-This is important: You are analyzing the persona of ${emailAccount.email}. Look at what they write about, how they communicate, and who they interact with to determine their professional role.
-
-Here are the emails they've sent:
-<emails>
-${emails.map((e) => `<email>\n${stringifyEmail(e, 1000)}\n</email>`).join("\n")}
-</emails>`;
+  const prompt = `<user>
+<email>${emailAccount.email}</email>
+</user>
+<task>
+Analyze the persona of the user above. Determine their professional role by examining what they write about, how they communicate, and who they interact with.
+</task>
+<emails>
+${emails.map((e) => `<email>\n${stringifyEmail(e, 1000)}\n</email>`).join("\n")}
+</emails>`;

28-33: Constrain responsibilities to 3–5 items to guide the LLM output.

The description asks for 3–5 responsibilities, but the schema doesn’t enforce it. Add min/max constraints to improve output reliability.

Outside the changed lines, update the schema as follows:

responsibilities: z
  .array(z.string().min(1))
  .min(3)
  .max(5)
  .describe(
    "An array of 3-5 key responsibilities evident from their email patterns and communications",
  ),

104-111: Add retry logic around the model call to handle transient failures.

Per our AI guidelines, add retries with jitter/backoff. You already depend on p-retry.

Outside the changed lines, consider:

import pRetry from "p-retry";

// ...

const result = await pRetry(
  () =>
    generateObject({
      ...modelOptions,
      system,
      prompt,
      schema: personaAnalysisSchema,
    }),
  {
    retries: 2,
    factor: 2,
    minTimeout: 500,
    maxTimeout: 2500,
  },
);

Also consider logging success/failure at debug level with non-PII metadata (email count, model name).

apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

87-97: Tighten the schema to reduce empty or verbose responses.

Make strings non-empty and keep the reason concise, aligning with your instructions.

Apply this diff to the schema:

-    schema: z.object({
-      reason: z
-        .string()
-        .describe("The reason you chose the rule. Keep it concise"),
-      ruleName: z
-        .string()
-        .describe("The exact name of the rule you want to apply"),
-      noMatchFound: z
-        .boolean()
-        .describe("True if no match was found, false otherwise"),
-    }),
+    schema: z.object({
+      reason: z
+        .string()
+        .min(1)
+        .max(200)
+        .describe("The reason you chose the rule. Keep it concise"),
+      ruleName: z
+        .string()
+        .min(1)
+        .describe("The exact name of the rule you want to apply"),
+      noMatchFound: z
+        .boolean()
+        .describe("True if no match was found, false otherwise"),
+    }),
apps/web/utils/actions/cold-email.ts (1)

106-132: Guard against missing ctx.provider (runtime failure risk) and ensure safe-action client binds it

Provider was previously sourced from the DB; now it’s read from ctx. If the client forgets to bind provider, createEmailProvider may throw. Add an explicit guard (and optionally a fallback) to fail fast with a clear error.

Apply this minimal guard to prevent silent failures:

     async ({
-      ctx: { emailAccountId, provider },
+      ctx: { emailAccountId, provider },
       parsedInput: {
         from,
         subject,
         textHtml,
         textPlain,
         snippet,
         threadId,
         messageId,
         date,
       },
     }) => {
       const emailAccount = await prisma.emailAccount.findUnique({
         where: { id: emailAccountId },
         include: {
           user: { select: { aiProvider: true, aiModel: true, aiApiKey: true } },
         },
       });
 
       if (!emailAccount) throw new SafeError("Email account not found");
 
+      if (!provider) {
+        throw new SafeError(
+          "Missing provider in action context. Ensure the safe action client binds `provider` when invoking this action.",
+        );
+      }
+
       const emailProvider = await createEmailProvider({
         emailAccountId,
-        provider,
+        provider,
       });

If you want a resilient fallback instead of throwing, include account: { select: { provider: true } } in the findUnique include and use provider ?? emailAccount.account?.provider. I can draft that patch if preferred.

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

41-51: Schema/typing mismatch: CreateRulesOnboardingBody is array-based, but component treats it like a keyed object.

The new CreateRulesOnboardingBody is an array (see rule.validation), yet the component still uses keyed semantics via keyof and per-field IDs. This leads to incorrect typing and brittle assertions. Since this UI no longer edits per-category actions, consider removing keyed props and aligning to the array shape, or migrate to the new OnboardingCategories flow.

Apply this diff to decouple from keyed types and avoid misleading assertions:

-}: {
-  defaultValues: CreateRulesOnboardingBody;
-}) {
+}: {
+  defaultValues: CreateRulesOnboardingBody;
+}) {
   const router = useRouter();
   const { emailAccountId } = useAccount();

   const [showExampleDialog, setShowExampleDialog] = useState(false);

   const form = useForm<CreateRulesOnboardingBody>({
     resolver: zodResolver(createRulesOnboardingBody),
-    defaultValues,
+    defaultValues,
   });
apps/web/app/(app)/[emailAccountId]/onboarding/StepWho.tsx (1)

60-83: Scrolling to selected role uses mismatched indexing and may scroll to the wrong item.

You compute the selected index from USER_ROLES but render roles from usersRolesInfo (object entry order can differ). Prefer selecting by data attribute.

-  useEffect(() => {
-    if (defaultRole && scrollContainerRef.current) {
-      // Find the button with the selected role
-      const selectedIndex = USER_ROLES.findIndex(
-        (role) => role.value === defaultRole,
-      );
-      if (selectedIndex !== -1) {
-        const buttons = scrollContainerRef.current.querySelectorAll(
-          'button[type="button"]',
-        );
-        const selectedButton = buttons[selectedIndex];
-        if (selectedButton) {
-          // Use setTimeout to ensure the DOM is ready
-          setTimeout(() => {
-            selectedButton.scrollIntoView({
-              behavior: "smooth",
-              block: "center",
-            });
-          }, 100);
-        }
-      }
-    }
-  }, [defaultRole]);
+  useEffect(() => {
+    if (defaultRole && scrollContainerRef.current) {
+      const selectedButton = scrollContainerRef.current.querySelector(
+        `button[data-role="${CSS.escape(defaultRole)}"]`,
+      ) as HTMLButtonElement | null;
+      if (selectedButton) {
+        setTimeout(() => {
+          selectedButton.scrollIntoView({ behavior: "smooth", block: "center" });
+        }, 100);
+      }
+    }
+  }, [defaultRole]);
...
-            {Object.entries(usersRolesInfo).map(([roleName, role]) => {
+            {Object.entries(usersRolesInfo).map(([roleName, role]) => {
               const Icon = role.icon;
               const description = USER_ROLES.find(
                 (r) => r.value === roleName,
               )?.description;

               return (
                 <button
                   type="button"
                   key={roleName}
+                  data-role={roleName}
                   className={cn(
                     "rounded-xl border bg-card p-4 text-card-foreground shadow-sm text-left flex items-center gap-4 transition-all",
                     watchedRole === roleName &&
                       "border-blue-600 ring-2 ring-blue-100",
                   )}
                   onClick={() => {
                     setValue("role", roleName);
                     if (roleName !== "Other") {
                       setCustomRole("");
                     }
                   }}
                 >

Also applies to: 141-169

apps/web/app/api/user/categorization-preferences/route.ts (1)

51-71: Action required: onboarding name mismatch — GET returns RuleName (display) but onboarding action expects lowercase keys

Short: getUserPreferences() (GET) returns category.name values from RuleName (e.g. "Newsletter", "Cold Email"), but createRulesOnboardingAction expects the legacy lowercase keys ("newsletter", "coldEmail", "toReply", ...). This will cause the assistant onboarding flow (which fetches defaults from the GET and posts them back) to treat system categories as custom and skip applying settings.

Files to fix / inspect:

  • apps/web/app/api/user/categorization-preferences/route.ts — GET returns name: RuleName.*.
  • apps/web/utils/rule/consts.ts — RuleName values are display strings ("Newsletter", "Cold Email", ...).
  • apps/web/utils/actions/rule.ts — createRulesOnboardingAction uses systemCategories = ["toReply","newsletter",..."] and later indexes systemCategoryMap with RuleName.* (mismatch).
  • apps/web/app/(app)/[emailAccountId]/assistant/onboarding/page.tsx — fetches GET and passes defaultValues to the assistant form.
  • apps/web/app/(app)/[emailAccountId]/assistant/onboarding/CategoriesSetup.tsx — submits form data to createRulesOnboardingAction.
  • apps/web/utils/category-config.tsx — UI uses lowercase keys (categoryConfig.key).

Suggested fixes (pick one):

  • Preferable: normalize incoming names in createRulesOnboardingAction (map display names → legacy keys) before building systemCategoryMap, so the action accepts both display and legacy keys and remains backward-compatible.
  • Alternative: change the GET to return the lowercase category keys instead of RuleName display strings (make GET align with what the action expects).

Example normalization approach (implement in createRulesOnboardingAction):

  • Build a mapping from RuleName.* → lowercase key, normalize category.name = mapping[category.name] ?? category.name, then proceed using the normalized name and use the lowercase keys when reading systemCategoryMap.
🧹 Nitpick comments (28)
apps/web/app/(landing)/welcome/survey.ts (2)

75-75: Leverage literal inference with as const for stronger types and immutability.

This will infer literal unions for the question keys/types and the choice strings, improving DX and preventing accidental mutation.

-};
+} as const;

60-62: Minor copy nit: “HackerNews” ➜ “Hacker News”.

Common styling uses a space. Optional but improves polish.

-        "HackerNews",
+        "Hacker News",
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

118-173: Define and use a stable return type for aiChooseRule.

Current early return shape differs from the main return (missing rule). Define a single result type to make consumers safer.

Outside the changed lines, consider:

export type ChooseRuleResult<T> = { rule?: T; reason: string };

export async function aiChooseRule<T extends { name: string; instructions: string }>(/* ... */): Promise<ChooseRuleResult<T>> {
  // ...
  if (!rules.length) return { reason: "No rules" };
  // ...
  if (aiResponse.noMatchFound) return { rule: undefined, reason: "No match found" };
  // ...
  return { rule: selectedRule, reason: aiResponse?.reason ?? "" };
}
apps/web/prisma/schema.prisma (1)

96-116: Consider indexing role if you’ll query/filter by it.

If onboarding or analytics need fast lookups by EmailAccount.role (e.g., suggesting labels by role), add a simple index to avoid future full scans.

Outside the changed lines, you could add:

@@index([role])

to the EmailAccount model.

apps/web/app/(app)/[emailAccountId]/onboarding/ContinueButton.tsx (1)

15-21: Decide whether consumers can override size/variant; adjust spread order accordingly

Currently {...props} comes after size and variant, so consumers can override these defaults. If you intend to enforce sm + primaryBlue, place {...props} first.

Apply this diff if you want to make size/variant authoritative:

-export function ContinueButton(props: ButtonProps) {
-  return (
-    <Button size="sm" variant="primaryBlue" {...props}>
+export function ContinueButton(props: ButtonProps) {
+  return (
+    <Button {...props} size="sm" variant="primaryBlue">
       Continue <ArrowRightIcon className="size-4 ml-2" />
     </Button>
   );
 }
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)

390-391: Nice UX improvement: include rule name in delete confirmation

Including the rule name makes the action clearer to users.

If rule names may contain quotes, consider escaping them to avoid awkward visuals:

-`Are you sure you want to delete the rule "${rule.name}"?`
+`Are you sure you want to delete the rule "${rule.name.replaceAll('"', '\\"')}"?`
apps/web/app/(app)/[emailAccountId]/onboarding/StepIntro.tsx (1)

69-69: Consider the UX impact of switching from viewport-relative to fixed height.

Changing from h-[50vh] to h-[400px] provides consistency but may impact user experience on smaller screens where 400px could be too large, or on very large screens where it might appear too small compared to the previous 50% viewport height.

Consider using responsive height classes to maintain good UX across different screen sizes:

-    <CardBasic className="rounded-2xl shadow-none grid grid-cols-5 p-0 pl-4 pt-4 gap-8 h-[400px]">
+    <CardBasic className="rounded-2xl shadow-none grid grid-cols-5 p-0 pl-4 pt-4 gap-8 h-[300px] sm:h-[400px] lg:h-[450px]">
apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx (1)

17-30: Clean up commented authentication code.

The commented-out authentication and user validation code should be removed if it's no longer needed, or properly implemented if authentication is required for the onboarding flow.

Consider removing the commented code if authentication will be handled elsewhere:

-  // const session = await auth();
-
-  // if (!session?.user) redirect("/login");
-
-  // const user = await prisma.user.findUnique({
-  //   where: { id: session.user.id },
-  //   select: { completedAppOnboardingAt: true, surveyRole: true },
-  // });
-
-  // if (!user) redirect("/login");
-
-  // if (!searchParams.force && user.completedAppOnboardingAt) {
-  //   redirect(env.NEXT_PUBLIC_APP_HOME_PATH);
-  // }
apps/web/utils/actions/rule.validation.ts (1)

219-219: Consider renaming exported type to avoid confusion with UI CategoryConfig

This file exports a CategoryConfig type that may be conflated with UI-facing categoryConfig from apps/web/utils/category-config.tsx. Consider a more specific name (e.g., RulesOnboardingCategoryConfig) to reduce ambiguity across layers.

apps/web/app/(app)/[emailAccountId]/onboarding/StepLabels.tsx (1)

32-38: Add responsive sizes to Next/Image for better performance

Since the image is hidden below xl and fixed at 50vw on xl+, provide a sizes prop so Next can serve optimally sized images.

         <Image
           src="/images/assistant/labels.png"
           alt="Categorize your emails"
           width={1200}
           height={800}
-          className="mx-auto rounded border-4 border-blue-50 shadow-sm"
+          className="mx-auto rounded border-4 border-blue-50 shadow-sm"
+          sizes="(min-width: 1280px) 50vw, 0px"
         />
apps/web/app/(app)/[emailAccountId]/onboarding/config.ts (1)

21-27: Give role keys a typed union for stronger type-safety

Using Record<string, ...> allows typos and makes lookups less safe. Define a RoleName union and use it here. This preserves DX and refactors without runtime impact.

Apply this diff:

-export const usersRolesInfo: Record<
-  string,
-  {
-    icon: LucideIcon;
-    suggestedLabels?: { label: string; description: string }[];
-  }
-> = {
+export const usersRolesInfo: Record<
+  RoleName,
+  {
+    icon: LucideIcon;
+    suggestedLabels?: { label: string; description: string }[];
+  }
+> = {

And add these declarations above usersRolesInfo:

export const USER_ROLE_NAMES = [
  "Founder",
  "Executive",
  "Small Business Owner",
  "Software Engineer",
  "Assistant",
  "Investor",
  "Sales",
  "Marketing",
  "Customer Support",
  "Realtor",
  "Content Creator",
  "Consultant",
  "E-commerce",
  "Student",
  "Individual",
  "Other",
] as const;
export type RoleName = (typeof USER_ROLE_NAMES)[number];
apps/web/app/(app)/[emailAccountId]/assistant/onboarding/CategoriesSetup.tsx (3)

93-103: Remove unused/incorrect props when rendering CategoryCard (id, form).

CategoryCard no longer consumes id/form and the id type is incorrect for an array schema. Keep the props surface minimal and consistent.

           {categoryConfig.map((category) => (
             <CategoryCard
               key={category.key}
-              id={category.key as keyof CreateRulesOnboardingBody}
               label={category.label}
               tooltipText={category.tooltipText}
               Icon={category.Icon}
               iconColor={category.iconColor}
-              form={form}
             />
           ))}

56-59: Explicitly mark fire-and-forget server action to avoid unhandled Promise rejections.

The action is intentionally not awaited. Prefix with void so linters don’t flag unhandled Promises.

-      createRulesOnboardingAction(emailAccountId, data);
+      void createRulesOnboardingAction(emailAccountId, data);

29-35: Remove unused import and dead/commented code.

  • useDelayedActionsEnabled is imported but unused.
  • Large commented block adds noise. Prefer removing it or adding a TODO with a clear follow-up.
-import { useDelayedActionsEnabled } from "@/hooks/useFeatureFlags";

And consider deleting the long commented FormField block to keep this component lean.

Also applies to: 144-146, 160-202

apps/web/utils/actions/email-account.ts (2)

12-20: Consider cache invalidation after updating email account role.

If any pages or SWR keys depend on EmailAccount.role, revalidate relevant paths/tags to reflect changes immediately.

Example:

+import { revalidatePath } from "next/cache";
...
   await prisma.emailAccount.update({
     where: { id: emailAccountId },
     data: { role },
   });
+  revalidatePath(`/${emailAccountId}/onboarding`);

Adjust the path/tag to match your data fetching strategy (pages vs. route handlers vs. SWR tags).


47-53: Verify pagination limits and prompt size to control cost and latency.

  • getMessagesWithPagination({ maxResults: 200 }) may be heavy for some accounts. Consider making this configurable or sampling.
  • getEmailForLLM with maxLength: 2000 could still produce large prompts when multiplied across 200 emails.

Do you want me to add an optional input schema to analyzePersonaAction to accept maxResults and a sampling strategy and wire it through? Happy to send a follow-up diff.

Also applies to: 61-66

apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingCategories.tsx (4)

81-89: Mark background action as intentional and consider basic error handling.

Prefix with void to avoid unhandled Promise warnings and keep the intent clear. If you ever need UX feedback, await and use toastError/toastSuccess per guidelines.

-    // runs in background so we can move on to next step faster
-    createRulesOnboardingAction(emailAccountId, allCategories);
+    // runs in background so we can move on to next step faster
+    void createRulesOnboardingAction(emailAccountId, allCategories);

180-182: Button is outside a form; use type="button" to avoid misleading semantics.

There’s no

here, so submit type is unnecessary.

-          <ContinueButton type="submit" onClick={onSubmit} />
+          <ContinueButton type="button" onClick={onSubmit} />

146-163: Avoid O(n^2) lookups and ensure stable keys.

  • Repeated find inside map is minor here but easy to avoid with a map/dictionary.
  • Use a stable unique key (config.key) instead of label.
-          {basicCategories.map((category, index) => {
-            const config = categoryConfig.find((c) => c.key === category.name);
+          {basicCategories.map((category, index) => {
+            const config = categoryConfig.find((c) => c.key === category.name);
             if (!config) return null;
             return (
               <CategoryCard
-                key={config.label}
+                key={config.key}
                 index={index}
                 label={config.label}
                 description={config.tooltipText}
                 Icon={config.Icon}
                 iconColor={config.iconColor}
                 update={updateBasicCategory}
                 value={category.action}
               />
             );
           })}

Optional: Precompute a map: const configMap = useMemo(() => new Map(categoryConfig.map(c => [c.key, c])), []);


46-48: Remove stale “copy paste of old file” comment.

Keeps the file clean and avoids confusion.

-// copy paste of old file
 export function CategoriesSetup() {
apps/web/app/(app)/[emailAccountId]/onboarding/StepWho.tsx (2)

31-34: Fix redundant prop typing in function signature.

emailAccountId is already part of StepWhoProps. Remove the duplicate intersection.

-export function StepWho({
-  initialRole,
-  emailAccountId,
-}: StepWhoProps & { emailAccountId: string }) {
+export function StepWho({ initialRole, emailAccountId }: StepWhoProps) {

103-128: Add basic error handling on submission.

Per guidelines, prefer handling server action results (result?.serverError) and showing toastError/toastSuccess. Currently, any exception will crash the handler or silently navigate. Consider using next-safe-action’s client result pattern.

I can wire this up to use action results and toasts if you share the project’s toast utilities import path.

apps/web/utils/actions/rule.ts (5)

668-679: Local function name shadows imported deleteRule (readability/maintainability).

The inner async function deleteRule(...) shadows the imported deleteRule used earlier in this file. Rename to avoid confusion.

Apply this diff to rename the local function:

-    async function deleteRule(systemType: SystemType, emailAccountId: string) {
+    async function deleteSystemRule(systemType: SystemType, emailAccountId: string) {

And update its call sites accordingly in this block (e.g., where you currently call deleteRule(SystemType.NEWSLETTER, emailAccountId) etc.).


511-512: Type nit: Use Promise<void>[] instead of Promise<any>[].

Avoids leaking any and clarifies intent that side-effects are awaited but no values are consumed.

Apply this diff:

-    const promises: Promise<any>[] = [];
+    const promises: Promise<void>[] = [];

543-557: Background fetch lacks error capture; consider wrapping to avoid unhandled rejections in edge cases.

The fire-and-forget fetch runs in the .then chain and isn't awaited. If it fails (e.g., NEXT_PUBLIC_BASE_URL misconfig), errors are dropped. Add a .catch with logger.error for observability.

Apply this diff:

-        fetch(
+        fetch(
           `${env.NEXT_PUBLIC_BASE_URL}/api/reply-tracker/process-previous`,
           {
             method: "POST",
             headers: {
               "Content-Type": "application/json",
               [INTERNAL_API_KEY_HEADER]: env.INTERNAL_API_KEY,
             },
             body: JSON.stringify({
               emailAccountId,
             } satisfies ProcessPreviousBody),
           },
-        );
+        ).catch((error) => {
+          logger.error("Failed to process previous replies", { error });
+        });

786-794: Consider revalidating assistant/automation pages after onboarding changes.

Rules and prompts drive UI; revalidate relevant pages after the onboarding batch completes to ensure fresh state.

Apply this diff:

     await prisma.emailAccount.update({
       where: { id: emailAccountId },
       data: {
         rulesPrompt: `${emailAccount.rulesPrompt || ""}\n${rules
           .map((r) => `* ${r}`)
           .join("\n")}`.trim(),
       },
     });
+    revalidatePath(prefixPath(emailAccountId, "/assistant"));
+    revalidatePath(prefixPath(emailAccountId, "/automation"));

395-452: Delete action: minor cleanup opportunity and safety checks.

  • The permission check on Line 402 is redundant since the initial findUnique includes emailAccountId in the where clause. Not harmful, but unnecessary.
  • Consider guarding against accidentally clearing rulesPrompt if the AI returns an empty string (defensive check), although current generator returns the existing prompt when it can't find the rule.

You can safely remove the redundant permission check:

-      if (rule.emailAccountId !== emailAccountId)
-        throw new SafeError("You don't have permission to delete this rule");

Optionally, avoid setting an empty prompt:

-          await prisma.emailAccount.update({
+          if (updatedPrompt?.trim()) {
+            await prisma.emailAccount.update({
               where: { id: emailAccountId },
               data: { rulesPrompt: updatedPrompt },
-          });
+            });
+          }
apps/web/app/api/user/categorization-preferences/route.ts (1)

106-122: getToReplySetting: clarity suggestion.

Currently returns undefined when no rules exist, relying on the spread-site fallback. Consider always returning a CategoryConfig to simplify callers and types, e.g., { action: "none", hasDigest: false } when disabled/missing.

Example refactor (no diff applied here to keep behavior unchanged):

  • Return { action: "none", hasDigest: false } instead of undefined on empty rules and when not found.
  • Do the same for getRuleSetting and getColdEmailSetting.

Comment on lines +752 to +756
RuleName.Notification,
"Notifications: Alerts, status updates, or system messages",
"Label all notifications as @[Notifications]",
false,
notification.action,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent label: "Notifications" vs "Notification".

Prompt text uses "@[Notifications]" but the created label is "Notification". This will misalign prompt vs actual label.

Apply this diff to keep them consistent:

-        "Label all notifications as @[Notifications]",
+        "Label all notifications as @[Notification]",
📝 Committable suggestion

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

Suggested change
RuleName.Notification,
"Notifications: Alerts, status updates, or system messages",
"Label all notifications as @[Notifications]",
false,
notification.action,
RuleName.Notification,
"Notifications: Alerts, status updates, or system messages",
"Label all notifications as @[Notification]",
false,
notification.action,
🤖 Prompt for AI Agents
In apps/web/utils/actions/rule.ts around lines 752 to 756, the prompt references
"@[Notifications]" but the created label string is "Notification", causing
mismatch; update the label string to "Notifications" (plural) so the prompt and
actual label match, i.e., replace "Notification" with "Notifications" for this
rule entry.

Comment on lines 213 to 221
const categoryConfig = z.object({
action: categoryAction.optional(),
hasDigest: z.boolean().optional(),
action: categoryAction.nullish(),
hasDigest: z.boolean().nullish(),
name: z.string(),
description: z.string(),
});
export type CategoryConfig = z.infer<typeof categoryConfig>;

export const createRulesOnboardingBody = z.object({
toReply: categoryConfig,
newsletter: categoryConfig,
marketing: categoryConfig,
calendar: categoryConfig,
receipt: categoryConfig,
coldEmail: categoryConfig,
notification: categoryConfig,
});
export const createRulesOnboardingBody = z.array(categoryConfig);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Tighten onboarding schema: trim and bound strings; require at least one category

  • Enforce trimmed, non-empty, and reasonably bounded name/description to meet our “validate type, format, and length” guideline.
  • Ensure the onboarding payload has at least one category.

Apply this diff:

-const categoryConfig = z.object({
-  action: categoryAction.nullish(),
-  hasDigest: z.boolean().nullish(),
-  name: z.string(),
-  description: z.string(),
-});
+const categoryConfig = z.object({
+  action: categoryAction.nullish(),
+  hasDigest: z.boolean().nullish(),
+  name: z
+    .string()
+    .trim()
+    .min(1, "Please enter a name")
+    .max(40, "Please keep names under 40 characters"),
+  description: z
+    .string()
+    .trim()
+    .min(1, "Please enter a description")
+    .max(140, "Please keep descriptions under 140 characters"),
+});
 export type CategoryConfig = z.infer<typeof categoryConfig>;
 
-export const createRulesOnboardingBody = z.array(categoryConfig);
+export const createRulesOnboardingBody = z
+  .array(categoryConfig)
+  .min(1, "Please add at least one category");
📝 Committable suggestion

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

Suggested change
const categoryConfig = z.object({
action: categoryAction.optional(),
hasDigest: z.boolean().optional(),
action: categoryAction.nullish(),
hasDigest: z.boolean().nullish(),
name: z.string(),
description: z.string(),
});
export type CategoryConfig = z.infer<typeof categoryConfig>;
export const createRulesOnboardingBody = z.object({
toReply: categoryConfig,
newsletter: categoryConfig,
marketing: categoryConfig,
calendar: categoryConfig,
receipt: categoryConfig,
coldEmail: categoryConfig,
notification: categoryConfig,
});
export const createRulesOnboardingBody = z.array(categoryConfig);
const categoryConfig = z.object({
action: categoryAction.nullish(),
hasDigest: z.boolean().nullish(),
name: z
.string()
.trim()
.min(1, "Please enter a name")
.max(40, "Please keep names under 40 characters"),
description: z
.string()
.trim()
.min(1, "Please enter a description")
.max(140, "Please keep descriptions under 140 characters"),
});
export type CategoryConfig = z.infer<typeof categoryConfig>;
export const createRulesOnboardingBody = z
.array(categoryConfig)
.min(1, "Please add at least one category");
🤖 Prompt for AI Agents
In apps/web/utils/actions/rule.validation.ts around lines 213 to 221, tighten
the onboarding schema by changing name and description to trimmed, non-empty,
length-bounded strings (e.g., z.string().trim().min(1).max(100) for name and
z.string().trim().min(1).max(1000) for description) and require the onboarding
array to contain at least one category by using .min(1) on
createRulesOnboardingBody; keep action and hasDigest nullish as-is and update
the inferred types accordingly.

Comment on lines 12 to 18
export const categoryConfig: {
key: string;
label: string;
tooltipText: string;
Icon: React.ElementType;
iconColor: IconCircleColor;
}[] = [
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve CategoryKey union by switching to as const satisfies instead of a widening type

The explicit array type annotation widens key to string, so CategoryKey becomes string. Use satisfies with as const to preserve literal unions and keep strong typing across the app.

Apply this diff:

-export const categoryConfig: {
-  key: string;
-  label: string;
-  tooltipText: string;
-  Icon: React.ElementType;
-  iconColor: IconCircleColor;
-}[] = [
+export const categoryConfig = [
@@
-];
+] as const satisfies readonly {
+  key: string;
+  label: string;
+  tooltipText: string;
+  Icon: React.ElementType;
+  iconColor: IconCircleColor;
+}[];

Also applies to: 70-73

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/web/utils/category-config.tsx (1)

13-20: Preserve CategoryKey union by switching to as const satisfies (prevents key from widening to string)

With the explicit array type, the key property widens to string and CategoryKey becomes string, losing strong typing. Use as const satisfies to constrain the array while preserving literal keys.

-export const categoryConfig: {
-  key: string;
-  label: string;
-  tooltipText: string;
-  Icon: React.ElementType;
-  iconColor: IconCircleColor;
-  action: CategoryAction;
-}[] = [
+export const categoryConfig = [
@@
-];
+] as const satisfies readonly {
+  key: string;
+  label: string;
+  tooltipText: string;
+  Icon: React.ElementType;
+  iconColor: IconCircleColor;
+  action: CategoryAction;
+}[];

Also applies to: 79-81

🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/onboarding/config.ts (1)

25-26: Align icon type with the rest of the app for consistency

This file uses LucideIcon, while apps/web/utils/category-config.tsx uses React.ElementType for the Icon field. Consider standardizing on one (preferably the narrower LucideIcon if all icons come from lucide-react) to avoid friction in shared consumers.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 677549e and 3ef5e77.

📒 Files selected for processing (3)
  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingCategories.tsx (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts (1 hunks)
  • apps/web/utils/category-config.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingCategories.tsx
🧰 Additional context used
📓 Path-based instructions (13)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
  • apps/web/utils/category-config.tsx
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
  • apps/web/utils/category-config.tsx
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

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

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
**/*.{ts,tsx}

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

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

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
  • apps/web/utils/category-config.tsx
apps/web/app/(app)/*/**

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

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
  • apps/web/utils/category-config.tsx
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts
  • apps/web/utils/category-config.tsx
apps/web/**/*.tsx

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

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/utils/category-config.tsx
**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/utils/category-config.tsx
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/category-config.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/utils/category-config.tsx
**/*.{html,jsx,tsx}

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

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/utils/category-config.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Use as const instead of literal types and type annotations.

Applied to files:

  • apps/web/utils/category-config.tsx
🧬 Code Graph Analysis (1)
apps/web/utils/category-config.tsx (2)
apps/web/app/(app)/[emailAccountId]/onboarding/IconCircle.tsx (1)
  • IconCircleColor (55-55)
apps/web/utils/actions/rule.validation.ts (1)
  • CategoryAction (211-211)
⏰ 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). (3)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/web/app/(app)/[emailAccountId]/onboarding/config.ts (1)

3-20: *Lucide icon imports with Icon suffix are valid for your installed version — no change needed

Quick check: apps/web/package.json pins lucide-react@0.511.0. The project added support for *Icon-suffixed aliases around v0.456.0, so RocketIcon, BriefcaseIcon, StoreIcon, etc. are supported in 0.511.0 and the import in apps/web/app/(app)/[emailAccountId]/onboarding/config.ts is fine.

Relevant spots:

  • apps/web/package.json — "lucide-react": "0.511.0"
  • apps/web/app/(app)/[emailAccountId]/onboarding/config.ts — current import uses RocketIcon, BriefcaseIcon, …
  • Repo already uses suffixed imports in many files (e.g., apps/web/components/ViewEmailButton.tsx — MailIcon), indicating the naming style is in use.

If you prefer to follow the canonical docs, switch to unsuffixed names (Mail, Rocket, etc.) or add the provided module-declaration/re-export to opt into suffixed typings — otherwise no fix required.

apps/web/utils/category-config.tsx (1)

26-29: Resolved — categoryAction Zod enum includes required values

I verified apps/web/utils/actions/rule.validation.ts: the categoryAction z.enum contains "label", "label_archive", "label_archive_delayed", and "none", so server-side validation already accepts the values used in apps/web/utils/category-config.tsx.

  • Verified location: apps/web/utils/actions/rule.validation.ts — categoryAction z.enum (≈ lines 205–211)

Comment on lines +22 to +28
export const usersRolesInfo: Record<
string,
{
icon: LucideIcon;
suggestedLabels: { label: string; description: string }[];
}
> = {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve literal role key union and make labels immutable using as const satisfies; also export RoleName

The current Record<string, ...> widens keys to string, so downstream code loses the role name union. This hurts type safety (e.g., consumers can use any string). Use as const satisfies to keep literal keys while constraining the value shape, and export a RoleName union for reuse.

Apply:

-export const usersRolesInfo: Record<
-  string,
-  {
-    icon: LucideIcon;
-    suggestedLabels: { label: string; description: string }[];
-  }
-> = {
+export const usersRolesInfo = {
@@
-};
+} as const satisfies Record<
+  string,
+  {
+    icon: LucideIcon;
+    suggestedLabels: readonly { label: string; description: string }[];
+  }
+>;
+
+export type RoleName = keyof typeof usersRolesInfo;

Also applies to: 225-226

🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/onboarding/config.ts around lines 22 to
28 (and also apply same pattern at lines 225-226), usersRolesInfo is typed as
Record<string, ...> which widens keys to string; change usersRolesInfo to be a
const literal object whose keys remain literal by using "as const satisfies
Record<...>" so the shape is constrained but keys stay literal, ensure
suggestedLabels strings are made readonly by using literal tuples/objects (as
const) within the value, and export a RoleName type as "export type RoleName =
keyof typeof usersRolesInfo" for downstream reuse. Ensure the satisfies type
matches the existing value shape (icon: LucideIcon; suggestedLabels: readonly {
label: string; description: string }[]) and apply the same change at lines
225-226.

@elie222 elie222 changed the title WIP: new onboarding flow New onboarding flow Aug 17, 2025
@claude
Copy link

claude bot commented Aug 17, 2025

Claude finished @elie222's task —— View job


Code Review: PR #682 - New Onboarding Flow

I've completed a comprehensive review of this major refactor. This PR introduces a sophisticated multi-step onboarding flow with persona analysis and role-based suggestions. Here's my detailed analysis:

🏗️ Architecture & Implementation

Strengths:

  • Well-structured multi-step onboarding flow with clear separation of concerns
  • Proper use of Next.js App Router with server actions and client components
  • Integration with existing authentication and database architecture
  • Clean component composition pattern for onboarding steps

Areas for Improvement:

  • Consider adding step validation to prevent users from skipping required steps
  • The onboarding flow could benefit from progress persistence across page refreshes

🔒 Security Considerations

Critical Issues:

  1. Weak Input Validation (/apps/web/utils/actions/onboarding.validation.ts:5-6):

    questions: z.any(),
    answers: z.any(),

    Using z.any() bypasses validation entirely. This could allow malicious input injection.

  2. Type Coercion Without Validation (/apps/web/app/api/user/persona/route.ts:22):

    personaAnalysis: emailAccount?.personaAnalysis as PersonaAnalysis | null,

    Direct type assertion without runtime validation could lead to type safety issues.

Recommendations:

  • Replace z.any() with proper Zod schemas that validate expected structure
  • Add runtime validation for persona analysis data before type assertion
  • Implement input sanitization for user-provided role names in StepWho component

Performance Analysis

Positive Aspects:

  • Efficient use of SWR for data fetching with proper caching
  • Database queries are optimized with selective field retrieval
  • Server actions use proper authentication middleware

Potential Issues:

  1. No Error Boundaries: Missing error handling in onboarding components could crash the entire flow
  2. Unhandled Promise: The persona analysis action doesn't have proper error handling in the UI layer
  3. Multiple Database Writes: saveOnboardingAnswersAction performs both extraction logic and database writes in a single transaction

Recommendations:

  • Add error boundaries around onboarding steps
  • Implement loading states and error handling for persona analysis
  • Consider separating data processing from database operations for better testability

🧹 Code Quality

Good Practices:

  • Consistent use of TypeScript with proper type definitions
  • Clean separation between validation schemas and implementation
  • Proper use of React Hook Form with Zod validation
  • Well-organized file structure following Next.js conventions

Issues Identified:

  1. Console Usage: Found 149 console statements across the codebase - consider using proper logging
  2. Complex Extraction Logic: The extractSurveyAnswers function in onboarding.ts is quite complex and could be refactored
  3. Missing JSDoc: Complex functions like persona analysis would benefit from documentation

🐛 Potential Bugs & Issues

  1. Race Condition Risk: Multiple onboarding actions could potentially conflict if called simultaneously
  2. Incomplete Data Handling: The persona analysis endpoint doesn't handle the case where email account doesn't exist
  3. Navigation Logic: Users could potentially access onboarding steps out of order via direct URL manipulation

📝 Specific Recommendations

  1. Immediate Fixes Needed:

    • Replace z.any() with proper validation schemas
    • Add error boundaries and loading states
    • Implement proper error handling for server actions
  2. Enhancement Suggestions:

    • Add step progress indicators
    • Implement onboarding analytics to track completion rates
    • Add ability to resume onboarding from any step
    • Consider adding onboarding step validation
  3. Technical Debt:

    • Reduce console usage in favor of structured logging
    • Add comprehensive error handling patterns
    • Consider implementing optimistic updates for better UX

Overall Assessment

This is a well-architected feature that significantly improves the user onboarding experience. The codebase follows good practices overall, but there are several security and reliability concerns that should be addressed before merging.

Priority Actions:

  1. Fix input validation issues (security-critical)
  2. Add proper error handling throughout the flow
  3. Implement loading states for better UX
  4. Add comprehensive testing for the onboarding flow

The implementation shows good understanding of Next.js patterns and modern React practices. With the recommended fixes, this will be a solid addition to the application.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (1)

84-87: Remove console usage in client code (silent failure intended)

Per coding guidelines, avoid console usage. The comment already states this should fail silently.

Apply this diff:

-        .catch((error) => {
-          // Fail silently - persona analysis is optional enhancement
-          console.error("Failed to analyze persona:", error);
-        });
+        .catch(() => {
+          // Fail silently - persona analysis is optional enhancement
+        });
🧹 Nitpick comments (2)
apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (2)

69-74: Harden onCompleted: don’t block navigation if the action fails

If completedOnboardingAction throws, the user won’t navigate off the step. Wrap in try/catch and proceed regardless. Optionally surface a toast, but at minimum don’t trap the user.

Apply this diff:

   const onCompleted = useCallback(async () => {
     analytics.onComplete();
     markOnboardingAsCompleted(ASSISTANT_ONBOARDING_COOKIE);
-    await completedOnboardingAction();
-    onNext();
+    try {
+      await completedOnboardingAction();
+    } catch {
+      // Optionally toastError here; failure should not block progression
+    }
+    onNext();
   }, [onNext, analytics]);

31-46: Optional: build steps after callbacks for clarity

These closures reference onNext/onCompleted/data declared later. It works due to closures, but moving steps construction after the callbacks improves readability and reduces cognitive load.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2d57074 and b340751.

📒 Files selected for processing (1)
  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (1 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
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/**/*.tsx

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

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
**/*.{ts,tsx}

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

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

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/app/(app)/*/**

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

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/app/(app)/*/**/*.tsx

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

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/app/(app)/*/**/**/*.tsx

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

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
apps/web/app/**/*.tsx

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

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
**/*.{html,jsx,tsx}

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

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
PR: elie222/inbox-zero#537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
📚 Learning: 2025-08-10T22:08:49.243Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-10T22:08:49.243Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Use proper error types and logging in LLM-related code.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use Math.min and Math.max to clamp values when the result is constant.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific `emailAccountId` are needed. The `emailAccountId` must be bound when calling the action from the client.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx
🧬 Code Graph Analysis (1)
apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (13)
apps/web/app/(app)/[emailAccountId]/onboarding/StepIntro.tsx (1)
  • StepIntro (11-58)
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx (1)
  • StepFeatures (56-135)
apps/web/app/(app)/[emailAccountId]/onboarding/StepWho.tsx (1)
  • StepWho (24-202)
apps/web/app/(app)/[emailAccountId]/onboarding/StepLabels.tsx (1)
  • StepLabels (10-50)
apps/web/app/(app)/[emailAccountId]/onboarding/StepDraft.tsx (1)
  • StepDraft (12-83)
apps/web/app/(app)/[emailAccountId]/onboarding/StepCustomRules.tsx (1)
  • StepCustomRules (10-53)
apps/web/app/(app)/[emailAccountId]/onboarding/StepExtension.tsx (1)
  • StepExtension (12-69)
apps/web/hooks/usePersona.ts (1)
  • usePersona (4-6)
apps/web/hooks/useAnalytics.ts (1)
  • useOnboardingAnalytics (4-20)
apps/web/utils/path.ts (1)
  • prefixPath (1-4)
apps/web/utils/cookies.ts (2)
  • markOnboardingAsCompleted (4-6)
  • ASSISTANT_ONBOARDING_COOKIE (1-1)
apps/web/utils/actions/onboarding.ts (1)
  • completedOnboardingAction (10-17)
apps/web/utils/actions/email-account.ts (1)
  • analyzePersonaAction (22-73)
⏰ 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: Jit Security
  • GitHub Check: claude-review
🔇 Additional comments (1)
apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (1)

76-83: No changes needed for analyzePersonaAction call

The analyzePersonaAction is built with actionClient.bindArgsSchemas<[emailAccountId: z.ZodString]>([z.string()]), so it requires the emailAccountId as its first (and only) argument. Calling

analyzePersonaAction(emailAccountId)

is therefore correct.

@claude
Copy link

claude bot commented Aug 17, 2025

Claude finished @elie222's task —— View job


🔍 Code Review Complete

I've conducted a comprehensive review of the new onboarding flow implementation. Here's my detailed feedback:

Overall Assessment

This is a well-architected implementation of a multi-step onboarding flow with AI-powered persona analysis. The code quality is high with good separation of concerns and proper error handling.

📋 Detailed Findings

🏗️ Architecture & Design

✅ Strengths:

  • Clean multi-step wizard architecture with proper state management
  • Well-organized component structure following established patterns
  • Good separation between UI components and business logic
  • Proper use of React patterns (hooks, context, form handling)

🔒 Security Review

✅ Secure Implementation:

  • Server actions use proper authentication via actionClientUser
  • Input validation with Zod schemas (stepWhoSchema, saveOnboardingAnswersBody)
  • AI analysis operates on user's own emails with proper access controls
  • No sensitive data exposure in persona analysis prompts
  • Proper error boundaries prevent information leakage

⚠️ Minor Security Notes:

  • Email content is truncated to 2000 chars before AI analysis (good practice)
  • Persona analysis gracefully degrades on failure without exposing errors

🗄️ Database & Data Flow

✅ Well Designed:

  • personaAnalysis stored as JSON in EmailAccount model
  • Proper field extraction in saveOnboardingAnswersAction with null/undefined handling
  • Efficient caching: existing persona analysis is reused if available
  • Clean migration strategy with backward compatibility

Performance Considerations

✅ Optimized:

  • Persona analysis runs asynchronously in background (OnboardingContent.tsx:84-96)
  • Graceful degradation if persona analysis fails
  • Limited to 200 emails for analysis (reasonable scope)
  • Proper loading states and error boundaries

🐛 Potential Issues & Recommendations

Medium Priority Issues:

  1. Race Condition in StepWho (StepWho.tsx:41-53)

    // Potential issue: defaultRole initialization
    const isCustomRole = initialRole && !USER_ROLES.some((role) => role.value === initialRole);
    const defaultRole = isCustomRole ? "Other" : initialRole || "";

    Recommendation: Consider using useMemo to prevent recalculation on re-renders.

  2. Form State Management (StepWho.tsx:101-123)

    • Parallel promises could fail independently
    • Recommendation: Add individual error handling for each promise.

Low Priority Improvements:

  1. Error Messaging (StepFeatures.tsx:120-125)

    • Generic error toast might not be helpful to users
    • Recommendation: More specific error messages based on failure type.
  2. Validation Schema (onboarding.validation.ts:3-7)

    questions: z.any(),
    answers: z.any(),

    Recommendation: Replace z.any() with proper schema validation.

🧪 Testing Coverage

✅ Excellent Test Strategy:

  • Comprehensive AI persona tests with multiple scenarios
  • Edge case coverage (empty emails, mixed roles)
  • Proper mocking and timeout handling
  • Good separation of test data and logic

💻 Code Quality

✅ High Standards:

  • Consistent TypeScript usage with proper types
  • Good component composition and reusability
  • Proper error boundaries and loading states
  • Clean file organization and naming conventions

🎯 Summary & Recommendations

Overall: This is production-ready code with minor improvements suggested.

Immediate Actions (Optional):

  1. Add individual error handling in parallel promise execution
  2. Replace z.any() with proper validation schemas
  3. Consider useMemo for expensive computations in StepWho

Future Enhancements:

  1. Add more granular error messages
  2. Consider progressive enhancement for persona analysis
  3. Add telemetry for onboarding completion rates

Great work on this implementation! The persona analysis feature adds significant value while maintaining security and performance standards. 🚀


Review completed: Examined 15+ files including components, server actions, AI analysis, database schema, and tests.

@claude
Copy link

claude bot commented Aug 17, 2025

Claude finished @elie222's task —— View job


Code Review: PR #682 - New onboarding flow

Final Review Summary

This PR implements a comprehensive multi-step onboarding flow with AI-powered persona analysis. The implementation demonstrates solid engineering practices with room for some improvements.


🎯 Overall Architecture Assessment: 8/10

Strengths:

  • Clean separation between UI components and business logic
  • Proper step-based navigation with URL state management
  • Asynchronous persona analysis doesn't block user flow
  • Well-structured component hierarchy with reusable elements
  • Smart conditional rendering (e.g., extension step only for Google)

Areas for improvement:

  • Missing error boundaries for critical failures
  • Limited loading states between steps
  • Some tight coupling between analytics and navigation logic

🐛 Critical Issues Found

1. Import Error (Fixed)

✅ StepIntro.tsx exists and is properly imported - no runtime error

2. Race Condition Risk

⚠️ Multiple persona analysis requests could fire if user navigates quickly through steps
Fix: Add request deduplication in analyzePersonaAction

3. Schema Validation

⚠️ saveOnboardingAnswersBody uses z.any() for questions/answers
Fix: Define proper schemas for these fields


🔒 Security Assessment: 7/10

Good practices:

  • Server actions use proper validation schemas
  • Database queries scoped to user context
  • Email content truncated to prevent oversized payloads
  • No sensitive data exposed in client code

Concerns:

  • Custom role input lacks sanitization
  • No rate limiting on persona analysis (fetches 200 emails)
  • Broad z.any() validation could accept malicious data

🚀 Performance Assessment: 8/10

Optimizations:

  • Background persona analysis with graceful fallbacks
  • Efficient SWR caching and revalidation
  • Email content truncation for LLM processing
  • Batched database operations where possible

Recommendations:

  • Add debouncing to custom role input field
  • Implement timeout handling for persona analysis
  • Consider pagination for large email collections
  • Add loading indicators between steps

💾 Database Schema: 9/10

Excellent additions:

  • personaAnalysis JSON field enables flexible AI data storage
  • role field properly typed and positioned
  • Clean migration preserving existing data
  • Proper foreign key relationships maintained

Minor suggestions:

  • Consider length constraints on role field
  • Add database-level validation for persona structure
  • Index completedOnboardingAt for analytics queries

🎨 Code Quality: 8/10

Strengths:

  • Consistent TypeScript usage with proper typing
  • Good component composition patterns
  • Clean validation schemas with meaningful errors
  • Proper React patterns (hooks, form handling)

Improvements:

  • Extract magic numbers to constants
  • Decompose some complex functions
  • Add more comprehensive error handling
  • Improve user-facing error messages

📋 Final Recommendations

High Priority

  1. Add request deduplication for persona analysis to prevent race conditions
  2. Strengthen validation schemas - replace z.any() with proper types
  3. Add input sanitization for custom role fields
  4. Implement timeout handling for persona analysis

Medium Priority

  1. Add loading states between steps for better UX
  2. Implement error boundaries for critical component failures
  3. Add rate limiting for email fetching operations
  4. Improve error messages to be more user-friendly

Nice to Have

  1. Add debouncing to custom role input
  2. Extract constants for magic numbers (step counts, limits)
  3. Add comprehensive logging for analytics and debugging
  4. Consider pagination for large email collections

Approval Status

This PR represents a solid implementation of a complex feature with good architectural decisions. While there are some security and performance optimizations that should be addressed, the core implementation is sound and ready for deployment with the high-priority fixes.

Recommendation: Approve with requested changes - address the critical issues above before merging.

The onboarding flow will significantly improve user experience and the persona analysis feature adds valuable AI-powered insights. Great work on this comprehensive feature!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/web/utils/email/provider-types.ts (1)

5-7: Mirror the type predicate for Microsoft to improve DX

Same rationale as above; helps TS narrow the value after checks.

-export function isMicrosoftProvider(provider: string | null | undefined) {
+export function isMicrosoftProvider(provider: string | null | undefined): provider is "microsoft" {
   return provider === "microsoft";
 }
apps/web/providers/EmailProvider.tsx (1)

38-58: Tighten types in mapLabelColor (provider can be null/undefined; label shouldn’t be any)

  • The signature currently accepts provider: string but immediately guards for falsy. Make the type reflect possible null/undefined to match call sites.
  • Avoid any for label to preserve type safety. At minimum, type the color shape you actually use.

Apply within these lines:

-function mapLabelColor(provider: string, label: any): EmailLabel["color"] {
+function mapLabelColor(
+  provider: "google" | "microsoft" | null | undefined,
+  label: { color?: { textColor?: string | null; backgroundColor?: string | null } | string | null }
+): EmailLabel["color"] {
   if (!provider) {
     return undefined;
   }
 
   if (isGoogleProvider(provider)) {
-    return label.color;
+    // For Google, label.color is already the desired shape
+    return (label as { color?: { textColor?: string | null; backgroundColor?: string | null } }).color;
   } else if (isMicrosoftProvider(provider)) {
-    const presetColor = label.color as string;
+    const presetColor = label.color as string;
     const backgroundColor =
       OUTLOOK_COLOR_MAP[presetColor as keyof typeof OUTLOOK_COLOR_MAP] ||
       "#95A5A6"; // Default gray if preset not found
 
     return {
       backgroundColor,
       textColor: null,
     };
   }
 
   throw new Error(`Unsupported provider: ${provider}`);
 }
apps/web/utils/actions/whitelist.ts (2)

13-15: Guard logic is correct; consider combining checks for brevity

Functionally fine as-is. If you prefer a single early return:

-    if (!env.WHITELIST_FROM) return;
-    if (!isGoogleProvider(provider)) return;
+    if (!env.WHITELIST_FROM || !isGoogleProvider(provider)) return;

16-24: Optional: add defensive error handling around Gmail API call

next-safe-action will handle unexpected errors globally, but if you want a clearer failure signal for expected API issues, wrap with try/catch and rethrow a SafeError. Keep this lightweight if you don’t need custom messages.

-    const gmail = await getGmailClientForEmail({ emailAccountId });
-
-    await createFilter({
-      gmail,
-      from: env.WHITELIST_FROM,
-      addLabelIds: ["CATEGORY_PERSONAL"],
-      removeLabelIds: [GmailLabel.SPAM],
-    });
+    try {
+      const gmail = await getGmailClientForEmail({ emailAccountId });
+      await createFilter({
+        gmail,
+        from: env.WHITELIST_FROM,
+        addLabelIds: ["CATEGORY_PERSONAL"],
+        removeLabelIds: [GmailLabel.SPAM],
+      });
+    } catch (err) {
+      // Optional: import SafeError from your actions lib and include a concise message
+      // throw new SafeError("Failed to create Gmail whitelist filter");
+      throw err;
+    }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b340751 and 9506693.

📒 Files selected for processing (5)
  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/onboarding/StepCustomRules.tsx (1 hunks)
  • apps/web/providers/EmailProvider.tsx (2 hunks)
  • apps/web/utils/actions/whitelist.ts (1 hunks)
  • apps/web/utils/email/provider-types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(app)/[emailAccountId]/onboarding/StepCustomRules.tsx
  • apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.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
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
apps/web/**/*.tsx

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

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/providers/EmailProvider.tsx
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/providers/EmailProvider.tsx
**/*.{ts,tsx}

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

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

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/providers/EmailProvider.tsx
**/*.{html,jsx,tsx}

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

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/providers/EmailProvider.tsx
apps/web/utils/actions/**/*.ts

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

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/whitelist.ts
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

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

Files:

  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/whitelist.ts
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
apps/web/utils/**/*.ts

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

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

Files:

  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
🧠 Learnings (3)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/actions/whitelist.ts
  • apps/web/utils/email/provider-types.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
PR: elie222/inbox-zero#537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/providers/EmailProvider.tsx
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific `emailAccountId` are needed. The `emailAccountId` must be bound when calling the action from the client.

Applied to files:

  • apps/web/utils/actions/whitelist.ts
🧬 Code Graph Analysis (2)
apps/web/providers/EmailProvider.tsx (1)
apps/web/utils/email/provider-types.ts (1)
  • isGoogleProvider (1-3)
apps/web/utils/actions/whitelist.ts (3)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (46-86)
apps/web/env.ts (1)
  • env (16-225)
apps/web/utils/email/provider-types.ts (1)
  • isGoogleProvider (1-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-review
🔇 Additional comments (3)
apps/web/utils/email/provider-types.ts (1)

1-3: Add a type predicate to isGoogleProvider for better narrowing

  • Change the function signature so callers get control-flow type narrowing when checking for "google".
  • Optionally, centralize allowed providers with a union type:
    export type Provider = "google" | "microsoft";
  • Verified—no remaining references to isGmailProvider were found.
-export function isGoogleProvider(provider: string | null | undefined) {
+export function isGoogleProvider(provider: string | null | undefined): provider is "google" {
   return provider === "google";
 }
apps/web/providers/EmailProvider.tsx (1)

8-10: Import rename to isGoogleProvider is consistent with utils change

The import swap aligns with the provider helper rename and keeps semantics intact.

apps/web/utils/actions/whitelist.ts (1)

8-9: Import update to isGoogleProvider is correct

Matches the helper rename and keeps the Gmail-only guard explicit.

@claude
Copy link

claude bot commented Aug 17, 2025

Claude finished @elie222's task —— View job


Code Review: New Onboarding Flow (PR #682)

I've completed a comprehensive review of this substantial onboarding implementation. Here's my analysis:

Strengths

Architecture & Design

  • Well-structured multi-step flow: Clean separation of concerns with individual step components
  • Smart persona analysis: Excellent use of AI to pre-populate user roles based on email patterns
  • Robust data persistence: Dual storage approach (EmailAccount.role + legacy User.surveyRole) ensures backward compatibility
  • Graceful degradation: Persona analysis fails silently without blocking the onboarding flow

Code Quality

  • Type safety: Proper Zod schemas for validation throughout
  • Error handling: Good error boundaries and user feedback mechanisms
  • Clean components: Well-structured React components with proper hooks usage
  • Database design: Thoughtful schema additions with proper indexing

User Experience

  • Progressive disclosure: Each step builds upon the previous
  • Visual consistency: Good use of shared components (OnboardingWrapper, IconCircle)
  • Analytics integration: Comprehensive tracking with PostHog
  • Provider-specific logic: Conditional Extension step for Google users only

⚠️ Areas for Improvement

Security Considerations

  1. API Key exposure risk (apps/web/utils/ai/knowledge/persona.ts:96-102):

    const modelOptions = getModel(emailAccount.user);

    Ensure LLM API keys are properly sanitized before sending to client-side components.

  2. Email content exposure (apps/web/utils/actions/email-account.ts:57-59):

    const emails = messages.map((message) =>
      getEmailForLLM(message, { removeForwarded: true, maxLength: 2000 }),
    );

    Consider implementing additional PII filtering for email content sent to AI services.

Performance Concerns

  1. Expensive persona analysis (apps/web/utils/actions/email-account.ts:47-49):

    const messagesResponse = await emailProvider.getMessagesWithPagination({
      maxResults: 200,
    });

    Fetching 200 emails on first step load could be slow. Consider:

    • Reduce to 50-100 messages initially
    • Add loading states/skeleton UI
    • Implement background processing queue
  2. Potential race conditions (apps/web/app/(app)/[emailAccountId]/onboarding/OnboardingContent.tsx:84-96):

    useEffect(() => {
      if (clampedStep === 1 && !data?.personaAnalysis) {
        analyzePersonaAction(emailAccountId)
          .then(() => mutate())

    Multiple rapid navigations could trigger concurrent analysis requests.

Code Quality Issues

  1. Weak validation schema (apps/web/utils/actions/onboarding.validation.ts:3-7):

    export const saveOnboardingAnswersBody = z.object({
      surveyId: z.string().optional(),
      questions: z.any(), // ❌ Too permissive
      answers: z.any(),   // ❌ Too permissive
    });
  2. Complex data extraction logic (apps/web/utils/actions/onboarding.ts:28-97):
    The extractSurveyAnswers function is quite complex and could be refactored into smaller, testable functions.

  3. Missing error recovery in several places:

    • StepWho.tsx:117-120 - No rollback if one action succeeds and another fails
    • Persona analysis has no retry mechanism

🚀 Recommendations

High Priority

  1. Implement request deduplication for persona analysis to prevent concurrent calls
  2. Add proper loading states for the persona analysis step
  3. Strengthen validation schemas - replace z.any() with specific types
  4. Add error recovery logic for failed server actions

Medium Priority

  1. Optimize persona analysis:

    • Reduce initial email fetch to 50-100 messages
    • Add background job processing for heavy analysis
    • Implement caching strategy
  2. Improve error handling:

    • Add retry mechanisms for failed API calls
    • Better user feedback for transient errors
    • Rollback strategies for partial failures
  3. Performance optimizations:

    • Lazy load step components
    • Preload next step data
    • Add request cancellation for navigation

Low Priority

  1. Code organization:

    • Extract shared validation types
    • Create custom hooks for complex form logic
    • Add JSDoc comments for complex functions
  2. Testing considerations:

    • Add integration tests for the full onboarding flow
    • Mock AI services for consistent testing
    • Test error scenarios and edge cases

📊 Technical Debt Assessment

The implementation shows good architectural decisions overall. The main technical debt items are:

  • Overly permissive validation schemas
  • Complex data extraction logic that could be simplified
  • Missing retry/recovery mechanisms for AI-dependent features

🎯 Conclusion

This is a well-implemented feature that follows good practices for a complex multi-step flow. The persona analysis integration is particularly innovative. The main concerns are around performance optimization and error resilience, which are typical for AI-integrated features.

Recommendation: ✅ Approve with suggested improvements

The core implementation is solid and ready for production. The suggested improvements can be addressed in follow-up PRs to enhance robustness and performance.


🤖 Generated with Claude Code

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
apps/web/utils/actions/rule.validation.ts (2)

196-206: Tighten onboarding schema: trim and bound description; consider nullish for key

  • description should be trimmed, non-empty, and reasonably bounded (mirrors name).
  • Consider consistency: other fields use .nullish(); align key as well unless you specifically need null over undefined.

Apply this diff:

 const categoryConfig = z.object({
   action: categoryAction.nullish(),
   hasDigest: z.boolean().nullish(),
   name: z
     .string()
     .trim()
     .min(1, "Please enter a name")
     .max(40, "Please keep names under 40 characters"),
-  description: z.string(),
-  key: z.nativeEnum(SystemRule).nullable(),
+  description: z
+    .string()
+    .trim()
+    .min(1, "Please enter a description")
+    .max(140, "Please keep descriptions under 140 characters"),
+  key: z.nativeEnum(SystemRule).nullish(),
 });

209-209: Require at least one category in onboarding payload

This aligns with our “validate type, format, and length” guideline and prior feedback.

-export const createRulesOnboardingBody = z.array(categoryConfig);
+export const createRulesOnboardingBody = z
+  .array(categoryConfig)
+  .min(1, "Please add at least one category");
apps/web/app/api/user/categorization-preferences/route.ts (1)

71-117: Critical: Object spread on possibly-undefined will throw; add nullish fallbacks

Each helper can return undefined; spreading undefined throws at runtime and violates strict TS. Use a safe fallback.

Apply this diff:

   return [
     {
       name: RuleName.ToReply,
       key: SystemRule.ToReply,
       description: "",
-      ...getToReplySetting(emailAccount.rules),
+      ...(getToReplySetting(emailAccount.rules) ?? {}),
     },
     {
       name: RuleName.ColdEmail,
       key: SystemRule.ColdEmail,
       description: "",
-      ...getColdEmailSetting(
-        emailAccount.coldEmailBlocker,
-        emailAccount.coldEmailDigest,
-      ),
+      ...(getColdEmailSetting(
+        emailAccount.coldEmailBlocker,
+        emailAccount.coldEmailDigest,
+      ) ?? {}),
     },
     {
       name: RuleName.Newsletter,
       key: SystemRule.Newsletter,
       description: "",
-      ...getRuleSetting(SystemType.NEWSLETTER, emailAccount.rules),
+      ...(getRuleSetting(SystemType.NEWSLETTER, emailAccount.rules) ?? {}),
     },
     {
       name: RuleName.Marketing,
       key: SystemRule.Marketing,
       description: "",
-      ...getRuleSetting(SystemType.MARKETING, emailAccount.rules),
+      ...(getRuleSetting(SystemType.MARKETING, emailAccount.rules) ?? {}),
     },
     {
       name: RuleName.Calendar,
       key: SystemRule.Calendar,
       description: "",
-      ...getRuleSetting(SystemType.CALENDAR, emailAccount.rules),
+      ...(getRuleSetting(SystemType.CALENDAR, emailAccount.rules) ?? {}),
     },
     {
       name: RuleName.Receipt,
       key: SystemRule.Receipt,
       description: "",
-      ...getRuleSetting(SystemType.RECEIPT, emailAccount.rules),
+      ...(getRuleSetting(SystemType.RECEIPT, emailAccount.rules) ?? {}),
     },
     {
       name: RuleName.Notification,
       key: SystemRule.Notification,
       description: "",
-      ...getRuleSetting(SystemType.NOTIFICATION, emailAccount.rules),
+      ...(getRuleSetting(SystemType.NOTIFICATION, emailAccount.rules) ?? {}),
     },
   ];
🧹 Nitpick comments (3)
apps/web/app/api/user/categorization-preferences/route.ts (3)

16-19: Avoid type name collision with exported CategoryConfig; rename local type

This local CategoryConfig differs from the public CategoryConfig from rule.validation.ts and can confuse readers. Rename to CategorySettings (or similar) and adjust return types.

-type CategoryConfig = {
+type CategorySettings = {
   action: CategoryAction | undefined;
   hasDigest: boolean | undefined;
 };
 
@@
-function getToReplySetting(
+function getToReplySetting(
   rules: UserPreferences["rules"],
-): CategoryConfig | undefined {
+): CategorySettings | undefined {
@@
-function getRuleSetting(
+function getRuleSetting(
   systemType: SystemType,
   rules?: UserPreferences["rules"],
-): CategoryConfig | undefined {
+): CategorySettings | undefined {
@@
-function getColdEmailSetting(
+function getColdEmailSetting(
   setting?: ColdEmailSetting | null,
   hasDigest?: boolean,
-): CategoryConfig | undefined {
+): CategorySettings | undefined {

Also applies to: 120-123, 138-141, 155-159


120-136: Optional: compute To-Reply in a single pass over rules

Minor perf/readability: avoid two find() calls by deriving both flags in one loop.

-function getToReplySetting(
-  rules: UserPreferences["rules"],
-): CategoryConfig | undefined {
-  if (!rules.length) return undefined;
-  const rule = rules.find((rule) =>
-    rule.actions.some((action) => action.type === ActionType.TRACK_THREAD),
-  );
-  const replyRules = rules.find(
-    (rule) => rule.systemType === SystemType.TO_REPLY,
-  );
-  const hasDigest = replyRules?.actions.some(
-    (action) => action.type === ActionType.DIGEST,
-  );
-
-  if (rule) return { action: "label", hasDigest };
-  return { action: "none", hasDigest };
-}
+function getToReplySetting(
+  rules: UserPreferences["rules"],
+): CategorySettings | undefined {
+  if (!rules.length) return undefined;
+  let hasTrackThread = false;
+  let hasDigest = false;
+  for (const rule of rules) {
+    if (!hasTrackThread) {
+      hasTrackThread = rule.actions.some(
+        (a) => a.type === ActionType.TRACK_THREAD,
+      );
+    }
+    if (!hasDigest && rule.systemType === SystemType.TO_REPLY) {
+      hasDigest = rule.actions.some((a) => a.type === ActionType.DIGEST);
+    }
+    if (hasTrackThread && hasDigest) break;
+  }
+  return { action: hasTrackThread ? "label" : "none", hasDigest };
+}

71-117: Follow-up: If description becomes required/trimmed, populate it here

If you adopt the stricter schema (trim + min(1)), empty description "" will violate expectations downstream. Consider supplying non-empty copy derived from a constants map (pure TS, no React imports), e.g., a new utils/rule/metadata.ts that maps SystemRule -> description.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9506693 and 9e60f29.

📒 Files selected for processing (2)
  • apps/web/app/api/user/categorization-preferences/route.ts (3 hunks)
  • apps/web/utils/actions/rule.validation.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/utils/actions/**/*.ts

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

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.validation.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)

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

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/utils/actions/*.validation.ts

📄 CodeRabbit Inference Engine (.cursor/rules/fullstack-workflow.mdc)

Define Zod schemas for validation in dedicated files and use them for both client and server validation.

Define input validation schemas using Zod in the corresponding .validation.ts file. These schemas are used by next-safe-action (.schema()) and can also be reused on the client for form validation.

Files:

  • apps/web/utils/actions/rule.validation.ts
**/*.{ts,tsx}

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

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

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.validation.ts
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/actions/rule.validation.ts
apps/web/utils/**/*.ts

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

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

Files:

  • apps/web/utils/actions/rule.validation.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/user/categorization-preferences/route.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/user/categorization-preferences/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit Inference Engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/user/categorization-preferences/route.ts
🧠 Learnings (4)
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod schemas for validation in dedicated files and use them for both client and server validation.

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use Zod schemas for validation on both client and server

Applied to files:

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

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
📚 Learning: 2025-08-10T22:08:49.243Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-10T22:08:49.243Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Make Zod schemas as specific as possible to guide the LLM output.

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
🧬 Code Graph Analysis (2)
apps/web/utils/actions/rule.validation.ts (2)
apps/web/utils/rule/consts.ts (2)
  • SystemRule (12-20)
  • SystemRule (21-21)
apps/web/utils/category-config.tsx (1)
  • categoryConfig (14-80)
apps/web/app/api/user/categorization-preferences/route.ts (2)
apps/web/utils/actions/rule.validation.ts (2)
  • CreateRulesOnboardingBody (210-212)
  • CategoryConfig (207-207)
apps/web/utils/rule/consts.ts (1)
  • RuleName (2-10)
⏰ 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: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/web/utils/actions/rule.validation.ts (1)

10-10: Import looks correct; using SystemRule as a value for z.nativeEnum is fine

z.nativeEnum accepts objects with string values; importing the value export aligns with the usage below.

apps/web/app/api/user/categorization-preferences/route.ts (1)

14-14: LGTM: Using RuleName and SystemRule for labeling output

This keeps the response aligned with the new array-based onboarding flow.

@claude
Copy link

claude bot commented Aug 17, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Aug 17, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@elie222 elie222 merged commit 13b030b into main Aug 17, 2025
7 of 10 checks passed
@elie222 elie222 deleted the feat/persona-analysis branch December 18, 2025 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments