Skip to content

Comments

Feature/outlook deep clean#874

Closed
salja03-t21 wants to merge 7 commits intoelie222:mainfrom
salja03-t21:feature/outlook-deep-clean
Closed

Feature/outlook deep clean#874
salja03-t21 wants to merge 7 commits intoelie222:mainfrom
salja03-t21:feature/outlook-deep-clean

Conversation

@salja03-t21
Copy link

@salja03-t21 salja03-t21 commented Oct 29, 2025

Summary by CodeRabbit

  • New Features

    • Added Outlook email provider support for cleanup actions alongside Gmail.
    • Introduced preview functionality for cleanup operations before execution.
    • Added back navigation capability throughout the cleanup workflow steps.
  • Improvements

    • Deep clean feature is now accessible to all users (previously restricted).
    • Inbox cleaner enabled by default for all users.
    • Unsubscribe access made available to all users.
    • Enhanced component ref forwarding for better stability.

Implement complete Outlook integration for the deep-clean feature:

Backend Changes:
- Created /api/clean/outlook/route.ts with Graph API integration
- Implemented archive operation (moves to Archive folder)
- Implemented mark as read operation
- Added provider detection and routing in /api/clean/route.ts
- Provider-agnostic static rules (starred/flagged, sent, etc.)
- QStash integration with signature verification

UI Improvements:
- Removed premium banner from intro step
- Updated 'Starred emails' to 'Starred/Flagged emails'
- Provider-aware confirmation text (Gmail vs Outlook)
- Dynamic messaging for archive and mark-as-read operations

Development Changes:
- Commented out premium checks for testing
- QStash credentials configured
- All required environment variables set

Files Created:
- apps/web/app/api/clean/outlook/route.ts

Files Modified:
- apps/web/app/api/clean/route.ts
- apps/web/app/(app)/[emailAccountId]/clean/IntroStep.tsx
- apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
- apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
- apps/web/.env

Note: Premium checks are disabled for development/testing only.
Re-enable before production deployment.
- Added onPrevious() function to useStep hook
- Added Back buttons to all wizard steps:
  * ActionSelectionStep
  * CleanInstructionsStep
  * TimeRangeStep
  * ConfirmationStep
- Users can now navigate back through the wizard steps
- Improves UX by allowing users to correct mistakes without restarting
Temporarily commented out PremiumExpiredCard component in SideNav
to allow testing of Outlook deep-clean feature without premium restrictions.
This should be re-enabled before production deployment.
Temporarily disable premium banner by making PremiumAlertWithData return null.
Original code preserved in comment block for re-enabling before production.
This resolves the persistent premium banner issue in the deep-clean wizard.
Temporarily comment out premium validation (lines 41-43) to allow testing
the deep-clean wizard without premium subscription.
Original code preserved in comments for re-enabling before production.
- Both lines 87 and 153 now correctly access provider from account object
- Pattern matches usage throughout codebase (safe-action.ts, etc)
- Type definition includes provider in account select
- Refactored undoCleanInboxAction and changeKeepToDoneAction to use provider-agnostic EmailProvider pattern instead of Gmail-only functions
- Fixed Undo button to work with both Gmail and Outlook accounts
- Hidden 'Process Only These 50 Emails' button on preview page as requested
- Integrated useEmailStream hook to properly display emails in real-time via SSE
- Fixed React hydration errors in Radix UI components (DropdownMenu, Tooltip) by adding suppressHydrationWarning
- Fixed TypeScript error in LoadingContent by changing error prop from string | null to string | undefined
@vercel
Copy link

vercel bot commented Oct 29, 2025

@salja03-t21 is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Oct 29, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

This PR adds multi-provider email support scaffolding with a focus on Outlook integration alongside existing Gmail support. It refactors clean action workflows to be provider-agnostic, introduces Outlook folder utilities, adds backward navigation to the clean steps UI, disables premium features for testing, and includes IDE configuration files and documentation updates.

Changes

Cohort / File(s) Summary
IDE and Project Configuration
.idea/...gitignore, .idea/modules.xml, .idea/vcs.xml, .idea/copilot.data.migration.*, .idea/inbox-zero.iml, .idea/inspectionProfiles/Project_Default.xml
Added IntelliJ IDEA project configuration files including module definitions, VCS mapping, inspection profiles (ESLint, PyPep8 naming), and Copilot migration state markers.
Git Ignore Updates
.gitignore, docker-compose.yml
Fixed coverage directory entry in root .gitignore, added memory-bank folder ignore, removed database service dependencies from docker-compose web service.
Documentation
Cline_Notes/outlook-deep-clean-plan.md
Outlined multi-provider Outlook support scaffolding for deep-clean feature with provider-agnostic constants, helpers, and type extensions.
Email Provider Abstraction
apps/web/utils/email/constants.ts, apps/web/utils/email/types.ts
Introduced provider-agnostic email state enums (EmailState, EmailCategory), cross-provider state mappings for Gmail and Outlook, and InboxZeroFolder utilities; extended ParsedMessage with optional isFlagged property.
Outlook Utilities
apps/web/utils/outlook/folders.ts
Added Outlook folder helpers: getOrCreateInboxZeroFolder, moveMessageToFolder, markMessageAsRead, flagMessage, getWellKnownFolderId.
Clean Action Steps UI
apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx, apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx, apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx, apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx, apps/web/app/(app)/[emailAccountId]/clean/IntroStep.tsx, apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
Added backward navigation via onPrevious handler in useStep hook; updated step components to include Back buttons; added provider-aware messaging in ConfirmationStep; removed PremiumAlertWithData from IntroStep.
Clean Action Preview and Routing
apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx, apps/web/app/api/clean/route.ts, apps/web/app/api/clean/outlook/route.ts
Added new PreviewStep component for email preview workflow; refactored route.ts to support provider-agnostic message fetching and Qstash routing (Gmail vs Outlook endpoints); added new Outlook-specific API route with message move/read operations.
Clean Action Server Logic
apps/web/utils/actions/clean.ts
Replaced Gmail-specific imports and APIs with provider-based operations; removed premium checks; added provider-agnostic label creation, thread retrieval, and messaging operations; updated action signatures to include provider context.
UI Component Ref Forwarding
apps/web/components/ui/dropdown-menu.tsx, apps/web/components/ui/tooltip.tsx
Converted DropdownMenuTrigger and TooltipTrigger from plain aliases to React.forwardRef wrappers with explicit ref forwarding and suppressHydrationWarning.
Feature Toggles and Premium
apps/web/hooks/useFeatureFlags.ts, apps/web/utils/premium/index.ts, apps/web/utils/actions/premium.ts, apps/web/components/PremiumAlert.tsx
Disabled useCleanerEnabled feature flag check (always returns true); made hasUnsubscribeAccess always return true; made decrementUnsubscribeCreditAction a no-op; disabled PremiumAlertWithData rendering.
Navigation
apps/web/components/SideNav.tsx
Unconditionally enabled Deep Clean navigation; commented out PremiumExpiredCard, ReferralDialog, and Premium menu item for testing.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Clean UI
    participant Route as /api/clean
    participant Provider as EmailProvider
    participant Queue as Qstash Queue
    participant CleanAPI as /api/clean/[provider]

    User->>UI: Start clean workflow
    UI->>UI: Step through (Intro→TimeRange→...) with Back/Next
    User->>UI: Confirm action
    UI->>Route: POST request with action, timeRange, provider
    Route->>Provider: Fetch message (Gmail/Outlook based on provider)
    Route->>Route: Build cleanBody with provider-specific structure
    alt Provider is Gmail
        Route->>Queue: Publish to gmail-action-queue
    else Provider is Outlook
        Route->>Queue: Publish to outlook-action-queue
    end
    Queue->>CleanAPI: Route to /api/clean/gmail or /api/clean/outlook
    CleanAPI->>Provider: Move/Archive message, Mark read, etc.
    CleanAPI->>Route: Return result
    UI->>UI: Update preview or complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas requiring extra attention:
    • Provider-agnostic refactoring in apps/web/utils/actions/clean.ts — Multiple provider-based branching, label/folder creation logic, and Redis state updates across cleanInboxAction, undoCleanInboxAction, and changeKeepToDoneAction; verify correct fallback behavior for both Gmail and Outlook.
    • Outlook API route apps/web/app/api/clean/outlook/route.ts — New POST handler with token refresh, message move/read operations, and error handling; ensure proper state persistence and OAuth token management.
    • Provider-aware routing in apps/web/app/api/clean/route.ts — Conditional endpoint selection and Qstash publishing logic; verify correct queue key generation and payload structure for both providers.
    • Feature toggle changes — Multiple functions disabled/simplified for testing (useCleanerEnabled, hasUnsubscribeAccess, decrementUnsubscribeCreditAction, PremiumAlertWithData, SideNav items); confirm these are intentional and document when/how they will be re-enabled for production.
    • Outlook folder helpers and ParsedMessage type extension — New Outlook-specific utilities and type changes; validate folder API calls and flag property integration across components.

Possibly related PRs

  • Move folder action improvements #683 — Modifies apps/web/utils/outlook/folders.ts utilities and provider-aware checks; directly related to Outlook folder helper additions.
  • Add Premium Alert to Automation Page #496 — Modifies apps/web/components/PremiumAlert.tsx (same file that is disabled in this PR); directly related to premium alert component changes.
  • Cleaner #370 — Introduces the "cleaner" inbox-zero feature across the same modules (clean actions, UI steps, API routes, feature flags); coordinated foundational feature development.

Suggested reviewers

  • elie222
  • mosesjames7271-svg

Poem

🐰 Hops through folders both Gmail and Outlook so keen,
Back buttons bounce, clean steps now are seen,
Provider-agnostic whiskers twitch with delight,
From inbox to preview, the workflow takes flight!
Testing toggles hop on, premium sleeps tight, 🎯

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Feature/outlook deep clean" directly relates to the main change in the changeset. The raw summary shows that the primary objective is implementing Outlook support for the deep clean feature, including new provider-agnostic abstractions, Outlook-specific API routes, UI updates for Outlook messaging, and helper utilities. The title clearly captures this core feature addition. While the prefix "Feature/" is somewhat generic, the specific part "outlook deep clean" is concrete and specific enough that a teammate scanning the history would immediately understand this PR adds Outlook support to the deep clean email cleaning capability.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

@jit-ci
Copy link

jit-ci bot commented Oct 29, 2025

Hi, I’m Jit, a friendly security platform designed to help developers build secure applications from day zero with an MVS (Minimal viable security) mindset.

In case there are security findings, they will be communicated to you as a comment inside the PR.

Hope you’ll enjoy using Jit.

Questions? Comments? Want to learn more? Get in touch with us.

@salja03-t21
Copy link
Author

Closing this PR - it was created from the wrong branch. Opening a new PR with the correct commits.

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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/web/components/SideNav.tsx (1)

91-108: Remove unused provider dependency and unused import.

The provider variable is included in the useMemo dependency array at line 107 but is no longer referenced within the navItems array. Additionally, the isGoogleProvider import at line 59 is now unused since the Deep Clean item is no longer provider-gated.

Apply this diff to remove the unused dependency:

    ],
-   [currentEmailAccountId, provider],
+   [currentEmailAccountId],
  );

And remove the unused import at line 59:

 import { ReferralDialog } from "@/components/ReferralDialog";
-import { isGoogleProvider } from "@/utils/email/provider-types";
 import { NavUser } from "@/components/NavUser";
apps/web/components/PremiumAlert.tsx (1)

118-153: Critical: Test code being merged to main branch.

This component is disabled with comments stating "Temporarily disabled for testing" and "re-enable before production". This strongly suggests non-production-ready code is being merged to the main branch.

This is a blocking issue because:

  1. It disables premium upgrade prompts, affecting monetization
  2. The comment explicitly warns about production
  3. No feature flag or environment gating is used

Do not merge this PR until one of the following is implemented:

Option 1: Use feature flag (recommended)

 export function PremiumAlertWithData({
   className,
   activeOnly,
 }: {
   className?: string;
   activeOnly?: boolean;
 }) {
-  // Temporarily disabled for testing
-  return null;
-
-  /* Original code - re-enable before production
+  // Allow disabling via feature flag for testing
+  const disablePremiumAlerts = env.NEXT_PUBLIC_DISABLE_PREMIUM_ALERTS === "true";
+  if (disablePremiumAlerts) {
+    return null;
+  }
+
   const {
     hasAiAccess,
     isLoading: isLoadingPremium,
     isProPlanWithoutApiKey,
     tier,
     data,
   } = usePremium();
   // ... rest of original code
-  */
 }

Option 2: Keep in feature branch
If this is truly temporary testing code, keep it in the feature/outlook-deep-clean branch and don't merge to main until production-ready.

apps/web/utils/premium/index.ts (1)

78-84: Critical: Dead parameters and unrestricted access require proper feature gating.

The function accepts tier and unsubscribeCredits parameters that are passed from call sites but completely ignored in the implementation. This creates:

  1. Dead parameters — accepted but never used, misleading API contract
  2. No access control logic — despite parameters suggesting there should be
  3. Unrestricted access — all users gain access regardless of tier or credit balance

Multiple components (PremiumAlert, bulk-unsubscribe) depend on this function's return value for UI gating and feature control. Either:

  • Implement proper access control using the tier and unsubscribeCredits parameters, OR
  • Add environment-based gating as suggested in the review (e.g., checking an env flag for testing), OR
  • Remove the dead parameters and rename to reflect unrestricted access

Additionally, review decrementUnsubscribeCreditAction in apps/web/utils/actions/premium.ts which has the same issue and comment pattern.

apps/web/utils/actions/clean.ts (2)

125-129: Log uses stale nextPageToken variable.

You log nextPageToken before assigning it. Use pageToken in the log entry.

-          logger.info("Fetched threads", {
-            threadCount: threads.length,
-            nextPageToken,
-          });
+          logger.info("Fetched threads", {
+            threadCount: threads.length,
+            nextPageToken: pageToken,
+          });

321-331: changeKeepToDoneAction sets undone: true after applying action.

This flags the thread as undone even though you’re completing it. Set to false or omit.

-              undone: true,
+              undone: false,
♻️ Duplicate comments (5)
.idea/inbox-zero.iml (1)

1-12: Same IDE configuration concern as .idea/copilot.data.migration.edit.xml.

This module config file should not be committed unless there's a documented team decision to version .idea/* files.

.idea/vcs.xml (1)

1-6: Same IDE configuration concern.

This VCS mapping should be auto-generated by the IDE and not committed.

.idea/copilot.data.migration.agent.xml (1)

1-6: Same IDE Copilot migration state concern.

User/IDE-specific Copilot migration tracking should not be committed.

.idea/copilot.data.migration.ask.xml (1)

1-6: Same IDE Copilot migration state concern.

Do not commit IDE-specific migration state.

.idea/modules.xml (1)

1-8: Same IDE configuration concern.

This module registry ties together the committed .idea/*.iml files. Reconsider whether IDE module configurations belong in version control.

🧹 Nitpick comments (17)
apps/web/components/ui/tooltip.tsx (1)

31-42: Consider simplifying with React 19's ref-as-prop pattern.

The forwardRef implementation is correct, but React 19 treats ref as a regular prop, making forwardRef unnecessary. You can simplify this:

-const TooltipTrigger = React.forwardRef<
-  React.ElementRef<typeof TooltipPrimitive.Trigger>,
-  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
->((props, ref) => (
+function TooltipTrigger({
+  ref,
+  ...props
+}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
+  return (
   <TooltipPrimitive.Trigger
     ref={ref}
     data-slot="tooltip-trigger"
     suppressHydrationWarning
     {...props}
   />
-));
-TooltipTrigger.displayName = "TooltipTrigger";
+  );
+}

Based on library documentation (React 19 upgrade guide).

apps/web/components/ui/dropdown-menu.tsx (2)

11-21: Consider simplifying with React 19's ref-as-prop pattern.

Same as the TooltipTrigger refactor - the forwardRef implementation is correct, but React 19 treats ref as a regular prop, making forwardRef unnecessary:

-const DropdownMenuTrigger = React.forwardRef<
-  React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
-  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
->((props, ref) => (
+function DropdownMenuTrigger({
+  ref,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
+  return (
   <DropdownMenuPrimitive.Trigger
     ref={ref}
     suppressHydrationWarning
     {...props}
   />
-));
-DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;
+  );
+}

Based on library documentation (React 19 upgrade guide).


11-21: Optional: Consider consistency with TooltipTrigger implementation.

Minor implementation differences compared to TooltipTrigger:

  1. displayName: Uses DropdownMenuPrimitive.Trigger.displayName here vs the string "TooltipTrigger" in tooltip.tsx. Using the primitive's displayName (as done here) is more maintainable.
  2. data-slot attribute: TooltipTrigger includes data-slot="tooltip-trigger", but this component doesn't have an equivalent. Consider adding data-slot="dropdown-menu-trigger" if the data-slot pattern is intentional for consistency.
apps/web/components/SideNav.tsx (2)

232-233: Consider removing commented code or tracking as a TODO.

The PremiumExpiredCard is marked as "temporarily disabled for testing," but leaving commented code in the codebase can lead to confusion and code rot. If this change is temporary, consider opening an issue to track re-enabling it. Otherwise, remove both the commented code and the unused import at line 61.

If you'd like to track this as a TODO instead of leaving commented code, I can help create an issue. Would you like me to do that?


236-239: Remove commented code and unused import.

Similar to the PremiumExpiredCard above, this commented-out ReferralDialog should either be removed entirely (along with its import at line 58) or tracked as a TODO if it's meant to be temporary.

If removing, apply this diff:

-import { ReferralDialog } from "@/components/ReferralDialog";
Cline_Notes/outlook-deep-clean-plan.md (1)

237-238: Optional: Format URLs as proper markdown links.

The static analysis tool flagged bare URLs. While functional, using proper markdown link syntax improves readability:

-## Notes
-
-- Outlook Graph API documentation: https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview
-- Gmail API documentation: https://developers.google.com/gmail/api
+## Notes
+
+- [Outlook Graph API documentation](https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview)
+- [Gmail API documentation](https://developers.google.com/gmail/api)
apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx (1)

12-12: Guard against step underflow when navigating Back

onPrevious decrements blindly; if already at the first step, this can push step below the valid range.

Consider clamping inside useStep so all callers are safe:

 export function useStep() {
   const [step, setStep] = useQueryState(
     "step",
     parseAsInteger
       .withDefault(CleanStep.INTRO)
       .withOptions({ history: "push", shallow: false }),
   );

   const onNext = useCallback(() => {
-    setStep(step + 1);
+    setStep(step + 1);
   }, [step, setStep]);

   const onPrevious = useCallback(() => {
-    setStep(step - 1);
+    const min = CleanStep.INTRO;
+    setStep(step > min ? step - 1 : min);
   }, [step, setStep]);

   return { step, setStep, onNext, onPrevious };
 }

Also applies to: 44-48

apps/web/utils/types.ts (1)

62-63: Unify provider semantics for starred/flagged

To avoid provider branching in consumers, prefer a neutral field name (e.g., isStarred) and map Outlook’s flag → true/false during parsing, or at least document the cross‑provider mapping at the interface level.

apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx (1)

133-146: Hide Gmail‑only onboarding link for non‑Google accounts

Per prior learning, the onboarding page is intentionally Gmail‑specific and should error for non‑Google accounts. Avoid confusing Outlook users by gating the "Edit settings" link to Gmail only. [Based on learnings]

-          <FooterLink
-            icon={SettingsIcon}
-            text="Edit settings"
-            href={prefixPath(emailAccountId, "/clean/onboarding")}
-          />
+          {isGmail && (
+            <FooterLink
+              icon={SettingsIcon}
+              text="Edit settings"
+              href={prefixPath(emailAccountId, "/clean/onboarding")}
+            />
+          )}
apps/web/app/api/clean/outlook/route.ts (2)

64-74: Clarify Outlook ID semantics (conversation vs message)

Comments contradict: threadId as conversationId vs messageId. Pick one and document; if using single-message ops, rename locally to messageId to avoid confusion.

-  // In Outlook, threadId is actually the conversationId
+  // In Outlook, our "threadId" payload carries a messageId (single message processing for now).
...
-  const messageId = threadId;
+  const messageId = threadId; // treat as messageId

75-95: Add idempotency for QStash retries (move-to-folder may fail on re-run)

QStash can retry on transient failures. Mark-as-read is idempotent, but moving an already moved message can 4xx. Handle "already in destination" errors gracefully.

Example pattern:

try {
  await moveMessageToFolder(outlook, messageId, WELL_KNOWN_FOLDERS.archive);
} catch (e: any) {
  const msg = String(e?.message ?? e);
  const alreadyMoved =
    msg.includes("already in the destination") || msg.includes("ErrorItemNotFound");
  if (!alreadyMoved) throw e;
  logger.info("Move skipped: already archived", { messageId });
}
apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx (1)

32-36: Avoid unsafe casts and non‑null assertions in query parsing.

  • Don't use non‑null assertion (!) per guidelines.
  • Validate action to known values; fallback safely.

Apply:

-  const action =
-    (searchParams.get("action") as CleanAction) ?? CleanAction.ARCHIVE;
-  const timeRange = searchParams.get("timeRange")
-    ? Number.parseInt(searchParams.get("timeRange")!)
-    : 7;
+  const actionParam = searchParams.get("action");
+  const action: CleanAction =
+    actionParam === CleanAction.MARK_READ ? CleanAction.MARK_READ : CleanAction.ARCHIVE;
+  const timeRangeParam = searchParams.get("timeRange");
+  const timeRange = timeRangeParam ? Number.parseInt(timeRangeParam, 10) : 7;

As per coding guidelines.

apps/web/app/api/clean/route.ts (2)

156-167: isSent() only checks Gmail label.

If Outlook parsing doesn’t inject a SENT label into labelIds, isSent will fail for Outlook. Either ensure the parser normalizes SENT into labelIds or extend check to Outlook’s from user mailbox heuristics.


248-263: Category gating is Gmail‑only; guard by provider.

You already note Outlook lacks these categories. To avoid accidental false positives if Outlook ever adds categories, guard with isGoogleProvider(...) explicitly.

apps/web/utils/email/constants.ts (2)

28-39: ARCHIVE label semantics for Gmail are not a real label.

Using "ARCHIVE" in GmailStateMap can mislead; in Gmail, archive = absence of INBOX. Represent ARCHIVE with a sentinel or handle via logic instead of a label string.

-  [EmailState.ARCHIVE]: "ARCHIVE", // Special: means "not INBOX"
+  [EmailState.ARCHIVE]: "__NOT_INBOX__", // sentinel; do not pass to Gmail APIs

And document that Gmail filtering should use “-label:INBOX”.


52-62: OutlookStateMap mixes folders with pseudo “flags”.

UNREAD/STARRED/IMPORTANT aren’t folders in Outlook. Mapping them to strings like UNREAD_FLAG risks accidental misuse. Prefer a shape that distinguishes folders vs flags, or remove non‑folder entries from this map.

Example:

export type OutlookStateMapping = {
  folder?: string;
  flag?: "unread" | "flagged" | "important";
};
export const OutlookStateMap: Record<EmailState, OutlookStateMapping> = {
  INBOX: { folder: "inbox" },
  SENT: { folder: "sentitems" },
  UNREAD: { flag: "unread" },
  STARRED: { flag: "flagged" },
  IMPORTANT: { flag: "important" },
  SPAM: { folder: "junkemail" },
  TRASH: { folder: "deleteditems" },
  DRAFT: { folder: "drafts" },
  ARCHIVE: { folder: "archive" },
};
apps/web/utils/actions/clean.ts (1)

115-120: Provider‑agnostic query leaks Gmail naming via labelId.

Using labelId for Outlook with "inbox" relies on provider interpretation. Prefer a neutral query shape (e.g., folderOrLabel: "inbox") or provider‑specific fields in the query object.

-                labelId: isGoogleProvider(provider)
-                  ? GmailLabel.INBOX
-                  : "inbox",
+                ...(isGoogleProvider(provider)
+                  ? { labelId: GmailLabel.INBOX }
+                  : { folder: "inbox" }),

And update EmailProvider.getThreadsWithQuery signature accordingly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f085fea and 6366e6d.

📒 Files selected for processing (32)
  • .gitignore (1 hunks)
  • .idea/.gitignore (1 hunks)
  • .idea/copilot.data.migration.agent.xml (1 hunks)
  • .idea/copilot.data.migration.ask.xml (1 hunks)
  • .idea/copilot.data.migration.ask2agent.xml (1 hunks)
  • .idea/copilot.data.migration.edit.xml (1 hunks)
  • .idea/inbox-zero.iml (1 hunks)
  • .idea/inspectionProfiles/Project_Default.xml (1 hunks)
  • .idea/modules.xml (1 hunks)
  • .idea/vcs.xml (1 hunks)
  • Cline_Notes/outlook-deep-clean-plan.md (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx (3 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/IntroStep.tsx (0 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx (1 hunks)
  • apps/web/app/api/clean/outlook/route.ts (1 hunks)
  • apps/web/app/api/clean/route.ts (8 hunks)
  • apps/web/components/PremiumAlert.tsx (2 hunks)
  • apps/web/components/SideNav.tsx (3 hunks)
  • apps/web/components/ui/dropdown-menu.tsx (1 hunks)
  • apps/web/components/ui/tooltip.tsx (2 hunks)
  • apps/web/hooks/useFeatureFlags.ts (1 hunks)
  • apps/web/utils/actions/clean.ts (6 hunks)
  • apps/web/utils/actions/premium.ts (1 hunks)
  • apps/web/utils/email/constants.ts (1 hunks)
  • apps/web/utils/outlook/folders.ts (1 hunks)
  • apps/web/utils/premium/index.ts (1 hunks)
  • apps/web/utils/types.ts (1 hunks)
  • docker-compose.yml (0 hunks)
💤 Files with no reviewable changes (2)
  • docker-compose.yml
  • apps/web/app/(app)/[emailAccountId]/clean/IntroStep.tsx
🧰 Additional context used
📓 Path-based instructions (29)
!{.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:

  • .idea/copilot.data.migration.ask2agent.xml
  • .idea/.gitignore
  • .idea/copilot.data.migration.agent.xml
  • .idea/copilot.data.migration.ask.xml
  • apps/web/utils/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • .idea/inspectionProfiles/Project_Default.xml
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • .idea/copilot.data.migration.edit.xml
  • apps/web/utils/types.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • .idea/modules.xml
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • .idea/vcs.xml
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/utils/email/constants.ts
  • Cline_Notes/outlook-deep-clean-plan.md
  • apps/web/components/ui/tooltip.tsx
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • .idea/inbox-zero.iml
  • apps/web/utils/outlook/folders.ts
  • .gitignore
  • apps/web/components/SideNav.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:

  • .idea/copilot.data.migration.ask2agent.xml
  • .idea/.gitignore
  • .idea/copilot.data.migration.agent.xml
  • .idea/copilot.data.migration.ask.xml
  • apps/web/utils/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • .idea/inspectionProfiles/Project_Default.xml
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • .idea/copilot.data.migration.edit.xml
  • apps/web/utils/types.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • .idea/modules.xml
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • .idea/vcs.xml
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/utils/email/constants.ts
  • Cline_Notes/outlook-deep-clean-plan.md
  • apps/web/components/ui/tooltip.tsx
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • .idea/inbox-zero.iml
  • apps/web/utils/outlook/folders.ts
  • .gitignore
  • apps/web/components/SideNav.tsx
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/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/types.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/utils/email/constants.ts
  • apps/web/components/ui/tooltip.tsx
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/components/SideNav.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/utils/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/types.ts
  • apps/web/utils/email/constants.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/utils/outlook/folders.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/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/types.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/utils/email/constants.ts
  • apps/web/components/ui/tooltip.tsx
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/components/SideNav.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/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/utils/types.ts
  • apps/web/utils/email/constants.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/outlook/folders.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/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/utils/types.ts
  • apps/web/utils/email/constants.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/outlook/folders.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/premium/index.ts
  • apps/web/utils/actions/premium.ts
  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/types.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/utils/email/constants.ts
  • apps/web/components/ui/tooltip.tsx
  • apps/web/utils/actions/clean.ts
  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/components/SideNav.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/premium.ts
  • apps/web/utils/actions/clean.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/premium.ts
  • apps/web/utils/actions/clean.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/clean/outlook/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
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/clean/outlook/route.ts
  • apps/web/app/api/clean/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/clean/outlook/route.ts
  • apps/web/app/api/clean/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/clean/outlook/route.ts
  • apps/web/app/api/clean/route.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/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/components/ui/tooltip.tsx
  • apps/web/components/SideNav.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]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/components/ui/tooltip.tsx
  • apps/web/components/SideNav.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]/clean/TimeRangeStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.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]/clean/TimeRangeStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.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]/clean/TimeRangeStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.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]/clean/TimeRangeStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.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]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/components/ui/tooltip.tsx
  • apps/web/components/SideNav.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]/clean/TimeRangeStep.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx
  • apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx
  • apps/web/components/ui/tooltip.tsx
  • apps/web/components/SideNav.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use React Hook Form with Zod validation for form handling

Use the LoadingContent component to handle loading and error states consistently in data-fetching components.

Use PascalCase for components (e.g. components/Button.tsx)

Files:

  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/components/ui/tooltip.tsx
  • apps/web/components/SideNav.tsx
apps/web/components/ui/**

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

Shadcn components are in components/ui

Files:

  • apps/web/components/ui/dropdown-menu.tsx
  • apps/web/components/ui/tooltip.tsx
apps/web/hooks/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use SWR for efficient data fetching and caching

apps/web/hooks/**/*.ts: Use SWR for client-side data fetching and caching.
Call mutate() after successful mutations to refresh SWR data on the client.

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Call mutate() after successful mutations to refresh data

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/hooks/**/*.{js,jsx,ts,tsx}

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

Place custom hooks in the apps/web/hooks/ directory.

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/hooks/use*.{js,jsx,ts,tsx}

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

apps/web/hooks/use*.{js,jsx,ts,tsx}: Name custom hooks with the use prefix (e.g., useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer using useSWR.
Create dedicated hooks for specific data types (e.g., useAccounts, useLabels).
Custom hooks should encapsulate reusable stateful logic, especially for data fetching or complex UI interactions.
Keep custom hooks focused on a single responsibility.

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/hooks/useFeatureFlags.ts

📄 CodeRabbit inference engine (.cursor/rules/posthog-feature-flags.mdc)

apps/web/hooks/useFeatureFlags.ts: All feature flag hooks should be defined in apps/web/hooks/useFeatureFlags.ts
Use kebab-case for feature flag keys (e.g., 'inbox-cleaner', 'pricing-options-2')
Use 'use[FeatureName]Enabled' for boolean flag hook names and 'use[FeatureName]Variant' for variant flag hook names
Always define types for variant flags
Always provide a default/control fallback for variant flags
Keep all feature flag hooks centralized in useFeatureFlags.ts

Files:

  • apps/web/hooks/useFeatureFlags.ts
🧠 Learnings (6)
📚 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]/clean/ConfirmationStep.tsx
📚 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/utils/email/constants.ts
  • apps/web/utils/actions/clean.ts
📚 Learning: 2025-07-18T15:07:12.415Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-07-18T15:07:12.415Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Keep all feature flag hooks centralized in useFeatureFlags.ts

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-07-18T15:07:12.415Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-07-18T15:07:12.415Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Use 'use[FeatureName]Enabled' for boolean flag hook names and 'use[FeatureName]Variant' for variant flag hook names

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-07-18T15:07:12.415Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-07-18T15:07:12.415Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : All feature flag hooks should be defined in apps/web/hooks/useFeatureFlags.ts

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-07-18T15:07:12.415Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-07-18T15:07:12.415Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Use kebab-case for feature flag keys (e.g., 'inbox-cleaner', 'pricing-options-2')

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
🧬 Code graph analysis (9)
apps/web/app/api/clean/outlook/route.ts (6)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/utils/outlook/client.ts (1)
  • getOutlookClientWithRefresh (84-156)
apps/web/utils/outlook/folders.ts (2)
  • moveMessageToFolder (175-183)
  • markMessageAsRead (188-196)
apps/web/utils/outlook/message.ts (1)
  • WELL_KNOWN_FOLDERS (72-79)
apps/web/utils/middleware.ts (1)
  • withError (283-288)
apps/web/app/api/clean/route.ts (10)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/utils/types.ts (1)
  • ParsedMessage (47-63)
apps/web/utils/email/provider-types.ts (1)
  • isGoogleProvider (1-3)
apps/web/utils/gmail/client.ts (1)
  • getGmailClientWithRefresh (49-103)
apps/web/utils/gmail/thread.ts (1)
  • getThreadMessages (140-149)
apps/web/utils/outlook/client.ts (1)
  • getOutlookClientWithRefresh (84-156)
apps/web/utils/gmail/label.ts (1)
  • GmailLabel (20-34)
apps/web/app/api/clean/gmail/route.ts (1)
  • CleanGmailBody (26-26)
apps/web/app/api/clean/outlook/route.ts (1)
  • CleanOutlookBody (29-29)
apps/web/utils/upstash/index.ts (1)
  • publishToQstash (14-35)
apps/web/app/(app)/[emailAccountId]/clean/TimeRangeStep.tsx (1)
apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx (1)
  • useStep (5-27)
apps/web/app/(app)/[emailAccountId]/clean/ActionSelectionStep.tsx (1)
apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx (1)
  • useStep (5-27)
apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx (5)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (79-89)
apps/web/utils/actions/clean.ts (1)
  • cleanInboxAction (27-180)
apps/web/app/(app)/[emailAccountId]/clean/useEmailStream.ts (1)
  • useEmailStream (8-154)
apps/web/components/LoadingContent.tsx (1)
  • LoadingContent (13-27)
apps/web/app/(app)/[emailAccountId]/clean/EmailFirehose.tsx (1)
  • EmailFirehose (13-178)
apps/web/app/(app)/[emailAccountId]/clean/CleanInstructionsStep.tsx (1)
apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx (1)
  • useStep (5-27)
apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx (3)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (79-89)
apps/web/app/(app)/[emailAccountId]/clean/useStep.tsx (1)
  • useStep (5-27)
apps/web/utils/email/provider-types.ts (1)
  • isGoogleProvider (1-3)
apps/web/utils/actions/clean.ts (6)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/utils/date.ts (1)
  • ONE_DAY_MS (6-6)
apps/web/utils/email/provider-types.ts (1)
  • isGoogleProvider (1-3)
apps/web/utils/gmail/label.ts (1)
  • GmailLabel (20-34)
apps/web/utils/label.ts (1)
  • inboxZeroLabels (30-61)
apps/web/utils/outlook/folders.ts (2)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-71)
apps/web/utils/email/microsoft.ts (1)
  • getOrCreateOutlookFolderIdByName (1284-1286)
🪛 LanguageTool
Cline_Notes/outlook-deep-clean-plan.md

[style] ~205-~205: Try moving the adverb to make the sentence clearer.
Context: ... provider-agnostic - ✅ Updated category filtering to gracefully handle Gmail-specific categories - ✅ Added isFlagged property to `Pars...

(SPLIT_INFINITIVE)

🪛 markdownlint-cli2 (0.18.1)
Cline_Notes/outlook-deep-clean-plan.md

184-184: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


194-194: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


200-200: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


237-237: Bare URL used

(MD034, no-bare-urls)


238-238: Bare URL used

(MD034, no-bare-urls)

⏰ 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: cubic · AI code reviewer
  • GitHub Check: Jit Security

Comment on lines +1 to +6
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project> No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Remove IDE configuration files from version control.

IDE configuration files in .idea/ should not be committed to version control. These files are machine-local and specific to IntelliJ IDEA users, and can cause unnecessary merge conflicts and impose IDE-specific settings on contributors who use other editors or IDEs.

Delete this file and ensure .idea/ is added to .gitignore if not already present.


🏁 Script executed:

#!/bin/bash
# Verify if .idea/ is in .gitignore
if [ -f .gitignore ]; then
  grep -q "^\.idea/" .gitignore && echo ".idea/ is in .gitignore" || echo ".idea/ is NOT in .gitignore"
else
  echo ".gitignore does not exist"
fi

Length of output: 27


Remove IDE configuration files from version control.

IDE configuration files in .idea/ should not be committed to version control. These files are machine-local and specific to IntelliJ IDEA users, causing unnecessary merge conflicts and imposing IDE-specific settings on contributors using other editors or IDEs.

  • Delete .idea/copilot.data.migration.ask2agent.xml
  • Add .idea/ to .gitignore (currently not present)
🤖 Prompt for AI Agents
.idea/copilot.data.migration.ask2agent.xml lines 1-6: this IDE-specific file
should be removed from source control and .idea/ should be ignored; delete
.idea/copilot.data.migration.ask2agent.xml from the repo, add a line ".idea/" to
the repository .gitignore (create .gitignore if missing), commit the deletion
and updated .gitignore in a single commit, and ensure you do not re-add the
.idea directory in subsequent commits (run git rm --cached if needed on other
.idea files).

Comment on lines +44 to +47
const runPreview = useCallback(async () => {
setIsLoading(true);
setError(null);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix: setError(null) violates declared type; use undefined.

error is declared as string | undefined but null is assigned. This will fail type‑checking.

Apply:

-    setError(null);
+    setError(undefined);
📝 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 runPreview = useCallback(async () => {
setIsLoading(true);
setError(null);
const runPreview = useCallback(async () => {
setIsLoading(true);
setError(undefined);
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/clean/PreviewStep.tsx around lines 44 to
47, the state setter setError is being called with null while the error state is
typed as string | undefined; change the call to setError(undefined) (or remove
the call and let the state remain undefined) so the assigned value matches the
declared type and TypeScript passes type-checking.

Comment on lines +78 to +85
await moveMessageToFolder({
client: outlook,
messageId,
destinationFolderId: WELL_KNOWN_FOLDERS.archive,
});
logger.info("Archived message", { messageId });
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix Outlook util calls: wrong argument shape and param name

moveMessageToFolder and markMessageAsRead expect positional args; current object style will fail. Also markMessageAsRead takes isRead boolean, not read.

Apply:

-      await moveMessageToFolder({
-        client: outlook,
-        messageId,
-        destinationFolderId: WELL_KNOWN_FOLDERS.archive,
-      });
+      await moveMessageToFolder(outlook, messageId, WELL_KNOWN_FOLDERS.archive);
...
-      await markMessageAsRead({
-        client: outlook,
-        messageId,
-        read: true,
-      });
+      await markMessageAsRead(outlook, messageId, true);

Also applies to: 88-94


🏁 Script executed:

#!/bin/bash
# Confirm util signatures to avoid regressions
rg -nC2 'export async function moveMessageToFolder\(' apps/web/utils/outlook
rg -nC2 'export async function markMessageAsRead\(' apps/web/utils/outlook

Length of output: 642


I need to see the complete function signatures. Let me fetch the full parameter lists:Let me get the complete function signatures with a direct file read:

[/run_scripts]

Now let me also check the actual usage in the route.ts file:

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Get the full function signatures
sed -n '175,185p' apps/web/utils/outlook/folders.ts
echo "---"
sed -n '188,198p' apps/web/utils/outlook/folders.ts

Length of output: 485


🏁 Script executed:

#!/bin/bash
# Check the actual calling code at lines 78-85 and 88-94
sed -n '75,95p' apps/web/app/api/clean/outlook/route.ts

Length of output: 535


Fix Outlook utility function calls: object-style arguments with wrong parameter name

The utility functions expect positional arguments, not objects. Additionally, markMessageAsRead uses the wrong parameter name (read instead of isRead).

Apply:

      await moveMessageToFolder({
-       client: outlook,
-       messageId,
-       destinationFolderId: WELL_KNOWN_FOLDERS.archive,
+       outlook,
+       messageId,
+       WELL_KNOWN_FOLDERS.archive
      });
      await markMessageAsRead({
-       client: outlook,
-       messageId,
-       read: true,
+       outlook,
+       messageId,
+       true
      });

Locations: lines 78–85 and 88–94.

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

🤖 Prompt for AI Agents
In apps/web/app/api/clean/outlook/route.ts around lines 78–85 and 88–94, the
Outlook utility functions are being called with object-style arguments and the
wrong parameter name; change those calls to use positional arguments in the
utilities' expected order and update the `markMessageAsRead` call to use the
`isRead` parameter name (or pass the boolean as the third positional arg).
Specifically, replace calls like moveMessageToFolder({ client: outlook,
messageId, destinationFolderId: WELL_KNOWN_FOLDERS.archive }) with the
positional form moveMessageToFolder(outlook, messageId,
WELL_KNOWN_FOLDERS.archive), and replace markMessageAsRead({ client: outlook,
messageId, read: true }) with markMessageAsRead(outlook, messageId, true) (use
`isRead` if using a named param instead of positional).

Comment on lines +109 to +115
await saveCleanResult({
emailAccountId,
threadId,
markDone,
jobId,
});
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Persist correct action: archived flag currently uses markDone

You set archive = markDone instead of shouldArchive, corrupting persisted state when the action is MARK_READ.

-    await saveCleanResult({
-      emailAccountId,
-      threadId,
-      markDone,
-      jobId,
-    });
+    await saveCleanResult({
+      emailAccountId,
+      threadId,
+      markDone,
+      jobId,
+      // pass whether we actually archived
+      archive: shouldArchive,
+    });

And adjust saveCleanResult signature:

-async function saveCleanResult({
-  emailAccountId,
-  threadId,
-  markDone,
-  jobId,
-}: {
-  emailAccountId: string;
-  threadId: string;
-  markDone: boolean;
-  jobId: string;
-}) {
+async function saveCleanResult({
+  emailAccountId,
+  threadId,
+  markDone,
+  jobId,
+  archive,
+}: {
+  emailAccountId: string;
+  threadId: string;
+  markDone: boolean;
+  jobId: string;
+  archive: boolean;
+}) {

Also applies to: 164-171

🤖 Prompt for AI Agents
In apps/web/app/api/clean/outlook/route.ts around lines 109-115 (and similarly
at 164-171), the persisted result is using archive = markDone which corrupts
state for the MARK_READ action; change the call to pass archive: shouldArchive
(not markDone), and update the saveCleanResult function signature/usages to
accept a shouldArchive/archive boolean instead of reusing markDone so the
archived flag reflects the actual intended archive action; ensure all callers
and the saveCleanResult implementation are updated consistently to use the new
parameter name and semantics.

Comment on lines +90 to +96
const gmail = await getGmailClientWithRefresh({
accessToken: emailAccount.tokens.access_token,
refreshToken: emailAccount.tokens.refresh_token,
expiresAt: emailAccount.tokens.expires_at,
emailAccountId,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Inconsistent expiresAt handling between Gmail and Outlook.

Gmail path passes tokens.expires_at directly; Outlook path calls .getTime(). This suggests mixed types (Date vs number) and risks wrong comparisons/refresh behavior.

  • Normalize to milliseconds everywhere via a helper (e.g., getExpiresAtMs).
  • Ensure getGmailClientWithRefresh and getOutlookClientWithRefresh both accept ms.

Example:

-    const gmail = await getGmailClientWithRefresh({
-      accessToken: emailAccount.tokens.access_token,
-      refreshToken: emailAccount.tokens.refresh_token,
-      expiresAt: emailAccount.tokens.expires_at,
-      emailAccountId,
-    });
+    const gmail = await getGmailClientWithRefresh({
+      accessToken: emailAccount.tokens.access_token,
+      refreshToken: emailAccount.tokens.refresh_token,
+      expiresAt: normalizeToMs(emailAccount.tokens.expires_at),
+      emailAccountId,
+    });
...
-    const outlook = await getOutlookClientWithRefresh({
+    const outlook = await getOutlookClientWithRefresh({
       accessToken: emailAccount.tokens.access_token,
       refreshToken: emailAccount.tokens.refresh_token,
-      expiresAt: emailAccount.tokens.expires_at?.getTime() || null,
+      expiresAt: normalizeToMs(emailAccount.tokens.expires_at),
       emailAccountId,
     });

Add:

function normalizeToMs(v?: number | Date | null): number | null {
  if (!v) return null;
  return typeof v === "number" ? v : v.getTime();
}

Also applies to: 101-105


🏁 Script executed:

#!/bin/bash
# Verify tokens.expires_at type across the codebase and Prisma schema
rg -nC2 'expires_at' --type=ts --type=prisma

Length of output: 30



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for expires_at in TypeScript files without type filter
rg -nC3 'expires_at' --type=ts

# Also search for .prisma files to locate schema
fd -e prisma

Length of output: 21511


🏁 Script executed:

#!/bin/bash
# Find and inspect the clean/route.ts file
fd -e ts -p 'clean/route'

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Search for getGmailClientWithRefresh and getOutlookClientWithRefresh function signatures
rg -nC5 'getGmailClientWithRefresh|getOutlookClientWithRefresh' --type=ts

Length of output: 17869


🏁 Script executed:

#!/bin/bash
# Look at the specific file context around lines 90-110
cat apps/web/app/api/clean/route.ts | head -110 | tail -50

Length of output: 1778


🏁 Script executed:

#!/bin/bash
# Get the full function signatures for both functions to confirm parameter types
rg -A10 'export const getGmailClientWithRefresh = async' apps/web/utils/gmail/client.ts

rg -A10 'export const getOutlookClientWithRefresh = async' apps/web/utils/outlook/client.ts

Length of output: 532


🏁 Script executed:

#!/bin/bash
# Check how emailAccount.tokens is fetched/typed in the clean/route.ts context
rg -B20 'const emailAccount = await getEmailAccountWithAiAndTokens' apps/web/app/api/clean/route.ts | head -40

Length of output: 551


Add .getTime() call to Gmail path to match Outlook and codebase pattern.

The review comment is correct. Both getGmailClientWithRefresh and getOutlookClientWithRefresh expect expiresAt: number | null, but the Gmail path passes a Date object directly while the Outlook path correctly converts it via .getTime(). All other call sites in the codebase consistently use .getTime() for this conversion.

    const gmail = await getGmailClientWithRefresh({
      accessToken: emailAccount.tokens.access_token,
      refreshToken: emailAccount.tokens.refresh_token,
-     expiresAt: emailAccount.tokens.expires_at,
+     expiresAt: emailAccount.tokens.expires_at?.getTime() || null,
      emailAccountId,
    });
📝 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 gmail = await getGmailClientWithRefresh({
accessToken: emailAccount.tokens.access_token,
refreshToken: emailAccount.tokens.refresh_token,
expiresAt: emailAccount.tokens.expires_at,
emailAccountId,
});
const gmail = await getGmailClientWithRefresh({
accessToken: emailAccount.tokens.access_token,
refreshToken: emailAccount.tokens.refresh_token,
expiresAt: emailAccount.tokens.expires_at?.getTime() || null,
emailAccountId,
});
🤖 Prompt for AI Agents
In apps/web/app/api/clean/route.ts around lines 90 to 96, the Gmail client call
passes a Date object for expiresAt instead of a numeric timestamp; change the
argument to pass a number like other call sites (e.g. use
emailAccount.tokens.expires_at?.getTime() or a conditional that converts the
Date to its getTime() value or null) so expiresAt becomes number | null as
expected by getGmailClientWithRefresh.

Comment on lines +305 to +310
if (actionLabel?.id) {
await emailProvider.labelMessage({
messageId: threadId,
labelId: actionLabel.id,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Likely ID mismatch: labelMessage receives messageId but you pass threadId.

If labelMessage expects a message id (common for Gmail APIs), passing threadId will fail or mislabel. Use the latest message id or a thread‑level labeling helper.

-          await emailProvider.labelMessage({
-            messageId: threadId,
-            labelId: actionLabel.id,
-          });
+          const messageId = await emailProvider.getLatestMessageId(threadId);
+          if (messageId) {
+            await emailProvider.labelMessage({ messageId, labelId: actionLabel.id });
+          }

Or expose labelThread on the provider.

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

🤖 Prompt for AI Agents
In apps/web/utils/actions/clean.ts around lines 305 to 310, the code calls
emailProvider.labelMessage with messageId set to threadId which likely
mismatches the API; replace threadId with the actual message id (e.g., the
latest messageId from the thread) or, if you intend to label the entire thread,
add and call a labelThread method on the provider. Ensure you obtain the correct
message id before calling labelMessage (or implement/expose labelThread and use
that) so the provider receives the expected identifier.

Comment on lines 38 to 43
export const decrementUnsubscribeCreditAction = actionClientUser
.metadata({ name: "decrementUnsubscribeCredit" })
.action(async ({ ctx: { userId } }) => {
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
premium: {
select: {
id: true,
unsubscribeCredits: true,
unsubscribeMonth: true,
lemonSqueezyRenewsAt: true,
stripeSubscriptionStatus: true,
},
},
},
});

if (!user) throw new SafeError("User not found");

const isUserPremium = isPremium(
user.premium?.lemonSqueezyRenewsAt || null,
user.premium?.stripeSubscriptionStatus || null,
);
if (isUserPremium) return;

const currentMonth = new Date().getMonth() + 1;

// create premium row for user if it doesn't already exist
const premium = user.premium || (await createPremiumForUser({ userId }));

if (
!premium?.unsubscribeMonth ||
premium?.unsubscribeMonth !== currentMonth
) {
// reset the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: {
// reset and use a credit
unsubscribeCredits: env.NEXT_PUBLIC_FREE_UNSUBSCRIBE_CREDITS - 1,
unsubscribeMonth: currentMonth,
},
});
} else {
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
return;

// decrement the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: { unsubscribeCredits: { decrement: 1 } },
});
}
// Premium enabled for all users permanently - no credit management needed
return;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Premium credit management completely disabled without proper feature gating.

This action now bypasses all unsubscribe credit management, effectively granting unlimited access to all users. This is a critical business logic change that:

  1. Removes monetization controls
  2. Bypasses premium tier restrictions
  3. Has no environment-based or feature-flag gating

If this is for testing, use environment variables or feature flags:

 export const decrementUnsubscribeCreditAction = actionClientUser
   .metadata({ name: "decrementUnsubscribeCredit" })
   .action(async ({ ctx: { userId } }) => {
-    // Premium enabled for all users permanently - no credit management needed
-    return;
+    // Feature flag or environment check for testing
+    if (env.DISABLE_PREMIUM_CHECKS === "true") {
+      return;
+    }
+    
+    // Original credit management logic here...
   });

If this is permanent, document the business decision and remove the dead code entirely rather than leaving commented logic that could confuse future developers.

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

🤖 Prompt for AI Agents
In apps/web/utils/actions/premium.ts around lines 38–43, the current action
unconditionally returns and disables unsubscribe credit management; reinstate
proper credit handling or gate the no-op behind a feature flag/env var. Update
the action to: check an environment variable or feature-flag (e.g.,
FEATURE_FREE_PREMIUM or PREMIUM_UNLIMITED) and only short-circuit when the flag
is set; otherwise perform the normal flow—validate userId, load and decrement
the user's unsubscribe credit in a transactional/atomic DB update, handle
zero-credit and missing-user cases, return an appropriate result, and log
errors; alternatively if making the no-op permanent delete the dead code and add
a clear comment and docs about the business decision.

Comment on lines +7 to +17
export enum EmailState {
INBOX = "INBOX",
SENT = "SENT",
UNREAD = "UNREAD",
STARRED = "STARRED", // Gmail: STARRED, Outlook: Flagged
IMPORTANT = "IMPORTANT",
SPAM = "SPAM",
TRASH = "TRASH",
DRAFT = "DRAFT",
ARCHIVE = "ARCHIVE", // Gmail: no INBOX label, Outlook: Archive folder
}
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 | 🟠 Major

Replace TypeScript enums with union types or const objects.

Guidelines prohibit enums in TS files. Use const objects + union types for better tree‑shaking and DX.

Apply:

-export enum EmailState {
-  INBOX = "INBOX",
-  SENT = "SENT",
-  UNREAD = "UNREAD",
-  STARRED = "STARRED",
-  IMPORTANT = "IMPORTANT",
-  SPAM = "SPAM",
-  TRASH = "TRASH",
-  DRAFT = "DRAFT",
-  ARCHIVE = "ARCHIVE",
-}
+export const EmailState = {
+  INBOX: "INBOX",
+  SENT: "SENT",
+  UNREAD: "UNREAD",
+  STARRED: "STARRED",
+  IMPORTANT: "IMPORTANT",
+  SPAM: "SPAM",
+  TRASH: "TRASH",
+  DRAFT: "DRAFT",
+  ARCHIVE: "ARCHIVE",
+} as const;
+export type EmailState = (typeof EmailState)[keyof typeof EmailState];
...
-export enum EmailCategory {
-  PERSONAL = "PERSONAL",
-  SOCIAL = "SOCIAL",
-  PROMOTIONS = "PROMOTIONS",
-  FORUMS = "FORUMS",
-  UPDATES = "UPDATES",
-}
+export const EmailCategory = {
+  PERSONAL: "PERSONAL",
+  SOCIAL: "SOCIAL",
+  PROMOTIONS: "PROMOTIONS",
+  FORUMS: "FORUMS",
+  UPDATES: "UPDATES",
+} as const;
+export type EmailCategory = (typeof EmailCategory)[keyof typeof EmailCategory];
...
-export enum InboxZeroFolder {
-  PROCESSED = "processed",
-  ARCHIVED = "archived",
-  MARKED_READ = "marked_read",
-}
+export const InboxZeroFolder = {
+  PROCESSED: "processed",
+  ARCHIVED: "archived",
+  MARKED_READ: "marked_read",
+} as const;
+export type InboxZeroFolder = (typeof InboxZeroFolder)[keyof typeof InboxZeroFolder];

As per coding guidelines.

Also applies to: 20-26, 79-83

Comment on lines +77 to +90
export const INBOX_ZERO_FOLDER_PREFIX = "Inbox Zero";

export enum InboxZeroFolder {
PROCESSED = "processed",
ARCHIVED = "archived",
MARKED_READ = "marked_read",
}

/**
* Get the full InboxZero folder/label name
*/
export function getInboxZeroFolderName(type: InboxZeroFolder): string {
return `${INBOX_ZERO_FOLDER_PREFIX}/${type}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

InboxZero naming may diverge from existing inboxZeroLabels.

This defines "Inbox Zero/" while inboxZeroLabels uses capitalized sublabels (e.g., ".../Processed", ".../Read"). This can create duplicate labels/folders per provider.

  • Reuse inboxZeroLabels as the single source of truth, or export common constants from one place and import here.
  • Align casing and naming across Gmail and Outlook.
    Based on learnings.
🤖 Prompt for AI Agents
In apps/web/utils/email/constants.ts around lines 77 to 90, the InboxZero folder
names are generated as "Inbox Zero/<lowercase>" which diverges from the existing
inboxZeroLabels that use capitalized sublabels and causes duplicate labels
across providers; replace the hardcoded enum/strings with a single shared source
(import inboxZeroLabels or move common constants to a shared module), ensure the
casing and exact label strings match that shared source, and update
getInboxZeroFolderName to construct/return the label using the imported/shared
constants so Gmail and Outlook use the same canonical names.

Comment on lines +175 to +238
export async function moveMessageToFolder(
client: OutlookClient,
messageId: string,
destinationFolderId: string,
): Promise<void> {
await client.getClient().api(`/me/messages/${messageId}/move`).post({
destinationId: destinationFolderId,
});
}

/**
* Mark a message as read or unread
*/
export async function markMessageAsRead(
client: OutlookClient,
messageId: string,
isRead: boolean,
): Promise<void> {
await client.getClient().api(`/me/messages/${messageId}`).patch({
isRead,
});
}

/**
* Flag (star) or unflag a message
* Equivalent to Gmail's starred label
*/
export async function flagMessage(
client: OutlookClient,
messageId: string,
isFlagged: boolean,
): Promise<void> {
await client
.getClient()
.api(`/me/messages/${messageId}`)
.patch({
flag: isFlagged
? { flagStatus: "flagged" }
: { flagStatus: "notFlagged" },
});
}

/**
* Get well-known folder IDs (inbox, sent, archive, etc.)
* These are standard folders that exist in all Outlook accounts
*/
export async function getWellKnownFolderId(
client: OutlookClient,
folderName:
| "inbox"
| "sentitems"
| "deleteditems"
| "drafts"
| "junkemail"
| "archive",
): Promise<string> {
const response = await client
.getClient()
.api(`/me/mailFolders/${folderName}`)
.select("id")
.get();

return response.id;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify error handling in Microsoft Graph API calls.

The new Outlook message operation functions (moveMessageToFolder, markMessageAsRead, flagMessage, getWellKnownFolderId) make direct API calls without visible error handling.

Ensure that:

  1. The OutlookClient class handles Microsoft Graph API errors appropriately
  2. Rate limiting is handled (Graph API has different limits than Gmail)
  3. Token expiration/refresh is managed
  4. Common errors (message not found, folder not found, permission denied) are handled gracefully

Check the error handling in the OutlookClient class:


🏁 Script executed:

#!/bin/bash
# Review OutlookClient error handling patterns
rg -n --type=ts -A10 -B5 'class OutlookClient' apps/web/utils/outlook/client.ts

# Check if there's centralized error handling for Graph API calls
rg -n --type=ts -C5 'GraphError|catch.*error' apps/web/utils/outlook/

Length of output: 20938


I need to examine the actual implementation of the functions mentioned in the review comment to verify current error handling status.


---

🏁 Script executed:

```shell
#!/bin/bash
# Get the actual content of the functions at lines 175-238 in folders.ts
sed -n '175,238p' apps/web/utils/outlook/folders.ts

Length of output: 76



---

🏁 Script executed:

```shell
#!/bin/bash
# First find the correct path to folders.ts
fd -type f -name "folders.ts" | grep outlook

# Then get the content at lines 175-238
if [ -f "apps/web/utils/outlook/folders.ts" ]; then
  sed -n '175,238p' apps/web/utils/outlook/folders.ts
fi

Length of output: 1568


Add error handling to Microsoft Graph API calls in message operation functions.

The four functions (moveMessageToFolder, markMessageAsRead, flagMessage, getWellKnownFolderId) at lines 175-238 make direct API calls without try/catch blocks, which will propagate unhandled errors on failure. This is inconsistent with error handling patterns used throughout the rest of the outlook utils and leaves the application vulnerable to unhandled promise rejections.

Wrap each function's API call with try/catch to handle:

  • 404 errors (message/folder not found)
  • 403 errors (permission denied)
  • 429 errors (rate limiting)
  • Other Graph API errors (token expiration, service errors)

Reference existing error handling patterns in the codebase (e.g., draft.ts, filter.ts) for consistency.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

5 issues found across 32 files

Prompt for AI agents (all 5 issues)

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


<file name="apps/web/components/PremiumAlert.tsx">

<violation number="1" location="apps/web/components/PremiumAlert.tsx:126">
Returning null here disables PremiumAlertWithData entirely, so premium alerts never render and users lose the upgrade prompt. Please restore the original logic before merging.</violation>
</file>

<file name="apps/web/utils/email/constants.ts">

<violation number="1" location="apps/web/utils/email/constants.ts:38">
Gmail does not expose an ARCHIVE label; mapping EmailState.ARCHIVE to the literal &quot;ARCHIVE&quot; will cause Gmail label operations to fail. Please represent archive by removing the INBOX label (or use a null sentinel) instead of a non-existent label name.</violation>
</file>

<file name="Cline_Notes/outlook-deep-clean-plan.md">

<violation number="1" location="Cline_Notes/outlook-deep-clean-plan.md:31">
The Stage 1 task list points to `apps/web/utils/outlook/folder.ts`, but the actual helper file is `apps/web/utils/outlook/folders.ts`, so the documentation should use the correct plural path.</violation>
</file>

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

<violation number="1" location="apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx:43">
`emailAccount?.provider` is always undefined because the provider value lives under `emailAccount.account.provider`, so Gmail accounts will incorrectly see the Outlook messaging. Replace this with the nested provider.</violation>
</file>

<file name="apps/web/app/api/clean/outlook/route.ts">

<violation number="1" location="apps/web/app/api/clean/outlook/route.ts:153">
Persistence logic for cleanup threads duplicates apps/web/app/api/clean/gmail/route.ts:saveToDatabase() function. This is an exact duplicate and should be extracted into a shared utility.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

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

activeOnly?: boolean;
}) {
// Temporarily disabled for testing
return null;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 29, 2025

Choose a reason for hiding this comment

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

Returning null here disables PremiumAlertWithData entirely, so premium alerts never render and users lose the upgrade prompt. Please restore the original logic before merging.

Prompt for AI agents
Address the following comment on apps/web/components/PremiumAlert.tsx at line 126:

<comment>Returning null here disables PremiumAlertWithData entirely, so premium alerts never render and users lose the upgrade prompt. Please restore the original logic before merging.</comment>

<file context>
@@ -122,6 +122,10 @@ export function PremiumAlertWithData({
   activeOnly?: boolean;
 }) {
+  // Temporarily disabled for testing
+  return null;
+
+  /* Original code - re-enable before production
</file context>
Fix with Cubic

[EmailState.SPAM]: "SPAM",
[EmailState.TRASH]: "TRASH",
[EmailState.DRAFT]: "DRAFT",
[EmailState.ARCHIVE]: "ARCHIVE", // Special: means "not INBOX"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 29, 2025

Choose a reason for hiding this comment

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

Gmail does not expose an ARCHIVE label; mapping EmailState.ARCHIVE to the literal "ARCHIVE" will cause Gmail label operations to fail. Please represent archive by removing the INBOX label (or use a null sentinel) instead of a non-existent label name.

Prompt for AI agents
Address the following comment on apps/web/utils/email/constants.ts at line 38:

<comment>Gmail does not expose an ARCHIVE label; mapping EmailState.ARCHIVE to the literal &quot;ARCHIVE&quot; will cause Gmail label operations to fail. Please represent archive by removing the INBOX label (or use a null sentinel) instead of a non-existent label name.</comment>

<file context>
@@ -0,0 +1,90 @@
+  [EmailState.SPAM]: &quot;SPAM&quot;,
+  [EmailState.TRASH]: &quot;TRASH&quot;,
+  [EmailState.DRAFT]: &quot;DRAFT&quot;,
+  [EmailState.ARCHIVE]: &quot;ARCHIVE&quot;, // Special: means &quot;not INBOX&quot;
+};
+
</file context>
Fix with Cubic

- Outlook folder mappings

2. Create Outlook folder helpers
- `apps/web/utils/outlook/folder.ts`
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 29, 2025

Choose a reason for hiding this comment

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

The Stage 1 task list points to apps/web/utils/outlook/folder.ts, but the actual helper file is apps/web/utils/outlook/folders.ts, so the documentation should use the correct plural path.

Prompt for AI agents
Address the following comment on Cline_Notes/outlook-deep-clean-plan.md at line 31:

<comment>The Stage 1 task list points to `apps/web/utils/outlook/folder.ts`, but the actual helper file is `apps/web/utils/outlook/folders.ts`, so the documentation should use the correct plural path.</comment>

<file context>
@@ -0,0 +1,239 @@
+   - Outlook folder mappings
+
+2. Create Outlook folder helpers
+   - `apps/web/utils/outlook/folder.ts`
+   - Outlook system folder operations
+   - `getOrCreateInboxZeroFolder()` equivalent
</file context>
Fix with Cubic

const { emailAccountId } = useAccount();
const { emailAccountId, emailAccount } = useAccount();
const { onPrevious } = useStep();
const isGmail = isGoogleProvider(emailAccount?.provider);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 29, 2025

Choose a reason for hiding this comment

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

emailAccount?.provider is always undefined because the provider value lives under emailAccount.account.provider, so Gmail accounts will incorrectly see the Outlook messaging. Replace this with the nested provider.

Prompt for AI agents
Address the following comment on apps/web/app/(app)/[emailAccountId]/clean/ConfirmationStep.tsx at line 43:

<comment>`emailAccount?.provider` is always undefined because the provider value lives under `emailAccount.account.provider`, so Gmail accounts will incorrectly see the Outlook messaging. Replace this with the nested provider.</comment>

<file context>
@@ -36,7 +38,9 @@ export function ConfirmationStep({
-  const { emailAccountId } = useAccount();
+  const { emailAccountId, emailAccount } = useAccount();
+  const { onPrevious } = useStep();
+  const isGmail = isGoogleProvider(emailAccount?.provider);
 
   const handleStartCleaning = async () =&gt; {
</file context>
Suggested change
const isGmail = isGoogleProvider(emailAccount?.provider);
const isGmail = isGoogleProvider(emailAccount?.account?.provider);
Fix with Cubic

]);
}

async function saveToDatabase({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 29, 2025

Choose a reason for hiding this comment

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

Persistence logic for cleanup threads duplicates apps/web/app/api/clean/gmail/route.ts:saveToDatabase() function. This is an exact duplicate and should be extracted into a shared utility.

Prompt for AI agents
Address the following comment on apps/web/app/api/clean/outlook/route.ts at line 153:

<comment>Persistence logic for cleanup threads duplicates apps/web/app/api/clean/gmail/route.ts:saveToDatabase() function. This is an exact duplicate and should be extracted into a shared utility.</comment>

<file context>
@@ -0,0 +1,183 @@
+  ]);
+}
+
+async function saveToDatabase({
+  emailAccountId,
+  threadId,
</file context>
Fix with Cubic

@salja03-t21 salja03-t21 deleted the feature/outlook-deep-clean branch November 3, 2025 01:00
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.

2 participants