Skip to content

Comments

fix: handle Outlook HTML content in draft similarity calculation#1196

Merged
elie222 merged 2 commits intomainfrom
fix/outlook-draft-similarity-score
Jan 4, 2026
Merged

fix: handle Outlook HTML content in draft similarity calculation#1196
elie222 merged 2 commits intomainfrom
fix/outlook-draft-similarity-score

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Jan 4, 2026

User description

Fixes draft matching/deletion failing for Microsoft Outlook users.

TLDR: The calculateSimilarity function wasn't handling Outlook HTML content, causing 0% match rate for Microsoft users vs 21% for Gmail.

Problem

  • Axiom logs showed Microsoft: 0% perfect matches out of 6,190 comparisons
  • Gmail: 21% perfect matches out of 1,304 comparisons
  • Drafts weren't being deleted because similarity scores were too low (0.08-0.75 instead of 1.0)

Root Cause

When bodyContentType === 'html' (Outlook), the textPlain field contains HTML that needs to be converted to plain text. The old calculateSimilarity passed this directly to parseReply, which doesn't handle HTML tags.

Changes

  • Update calculateSimilarity to accept ParsedMessage objects (with backwards compatibility for plain strings)
  • Use same normalization as isDraftUnmodified: convertEmailHtmlToText() + stripQuotedContent()
  • Pass full ParsedMessage from draft-tracking.ts instead of just textPlain
  • Add integration tests for Outlook HTML scenarios

Files Changed

  • apps/web/utils/similarity-score.ts - Updated function signature and normalization
  • apps/web/utils/reply-tracker/draft-tracking.ts - Pass full ParsedMessage
  • apps/web/utils/similarity-score.test.ts - Added integration tests

Generated description

Below is a concise technical summary of the changes proposed in this PR:

graph LR
trackSentDraftStatus_("trackSentDraftStatus"):::modified
calculateSimilarity_("calculateSimilarity"):::modified
cleanupThreadAIDrafts_("cleanupThreadAIDrafts"):::modified
normalizeStoredContent_("normalizeStoredContent"):::added
normalizeProviderContent_("normalizeProviderContent"):::added
CONVERT_EMAIL_HTML_TO_TEXT_("CONVERT_EMAIL_HTML_TO_TEXT"):::added
STRIP_QUOTED_CONTENT_("STRIP_QUOTED_CONTENT"):::added
STRING_SIMILARITY_("STRING_SIMILARITY"):::modified
trackSentDraftStatus_ -- "Now passes full message object to handle Outlook HTML." --> calculateSimilarity_
cleanupThreadAIDrafts_ -- "Now passes full draftDetails to handle HTML and quoted content." --> calculateSimilarity_
calculateSimilarity_ -- "Normalizes stored drafts via HTML-to-text and strip quoted." --> normalizeStoredContent_
calculateSimilarity_ -- "Normalizes provider messages, handling HTML and stripping quoted." --> normalizeProviderContent_
normalizeStoredContent_ -- "Converts stored draft HTML (<br>) to plain text." --> CONVERT_EMAIL_HTML_TO_TEXT_
normalizeStoredContent_ -- "Removes quoted reply blocks from stored draft content." --> STRIP_QUOTED_CONTENT_
normalizeProviderContent_ -- "Converts Outlook HTML provider bodies to plain text." --> CONVERT_EMAIL_HTML_TO_TEXT_
normalizeProviderContent_ -- "Strips quoted replies from provider message content." --> STRIP_QUOTED_CONTENT_
calculateSimilarity_ -- "Compares normalized texts with string-similarity for score." --> STRING_SIMILARITY_
classDef added stroke:#15AA7A
classDef removed stroke:#CD5270
classDef modified stroke:#EDAC4C
linkStyle default stroke:#CBD5E1,font-size:13px
Loading

Updates the calculateSimilarity function to properly handle HTML content from Outlook and other email providers, resolving issues where draft matching and deletion failed due to incorrect similarity scores. Modifies draft-tracking.ts to pass full ParsedMessage objects to the updated similarity calculation logic.

Latest Contributors(2)
UserCommitDate
elie222fix-up-imports-forNovember 22, 2025
mojkakec12345@gmail.comfix-webhook-email-proc...July 04, 2025
This pull request is reviewed by Baz. Review like a pro on (Baz).

Summary by CodeRabbit

  • Tests

    • Added broader, integration-style tests covering similarity comparisons, Outlook/Gmail HTML handling, quoted content, and edge cases for draft comparisons.
  • Refactor

    • Improved draft similarity and content normalization so drafts from HTML email clients are compared more accurately, reducing false modified/unmodified detections.

✏️ Tip: You can customize this high-level summary in your review settings.

- Update calculateSimilarity to accept ParsedMessage objects
- Convert Outlook HTML to plain text before comparison (bodyContentType='html')
- Use same normalization as isDraftUnmodified (stripQuotedContent, convertEmailHtmlToText)
- Add integration tests for Outlook and Gmail scenarios
- Fixes 0% match rate for Microsoft/Outlook drafts
@vercel
Copy link

vercel bot commented Jan 4, 2026

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

Project Deployment Review Updated (UTC)
inbox-zero Ready Ready Preview Jan 4, 2026 9:46pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

The PR refactors similarity calculation to accept full message/draft objects (including HTML) and adds normalization helpers for HTML-to-text conversion and quoted-content stripping. Draft-tracking callers were updated to pass full messages, and tests were expanded with integration-style cases for provider HTML and edge inputs.

Changes

Cohort / File(s) Summary
Similarity calculation refactoring
apps/web/utils/similarity-score.ts
Changed calculateSimilarity signature to `calculateSimilarity(storedContent?: string
Draft tracking updates
apps/web/utils/reply-tracker/draft-tracking.ts
Updated trackSentDraftStatus and cleanupThreadAIDrafts to pass full message/draft objects (e.g., ParsedMessage / draftDetails) into calculateSimilarity instead of plain extracted text, altering similarity checks to consider HTML/bodyContentType.
Test expansion
apps/web/utils/similarity-score.test.ts
Reworked tests to beforeAll, removed explicit parseReply mocks, added integration-style cases including Outlook/Gmail HTML handling, whitespace/special-character/empty-input scenarios, and a createParsedMessage helper.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • johnlowe399-blip
  • anakarentorosserrano-star

Poem

🐰✨
I hopped through HTML and quoted lines,
Turned tags to text and trimmed the fines,
Drafts and messages side by side,
Now similarity matches take a stride,
A rabbit cheers for cleaner ties 🌿

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and concisely describes the main change: fixing Outlook HTML content handling in draft similarity calculations, which is the core objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent 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 dfee416 and 2bf832a.

📒 Files selected for processing (2)
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Don't export imported variables
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.{tsx,ts,css}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't 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
Use semantic elements instead of role attributes in JSX
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid AR...

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use @/ path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css}

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

Format code with Prettier

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
apps/web/**/*.{example,ts,json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
🧠 Learnings (10)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused imports

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused labels

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused variables

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Remove redundant terms from logical expressions

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use useless undefined

Applied to files:

  • apps/web/utils/similarity-score.ts
🧬 Code graph analysis (2)
apps/web/utils/reply-tracker/draft-tracking.ts (1)
apps/web/utils/similarity-score.ts (1)
  • calculateSimilarity (44-76)
apps/web/utils/similarity-score.ts (3)
apps/web/utils/mail.ts (2)
  • convertEmailHtmlToText (105-126)
  • parseReply (9-13)
apps/web/utils/ai/choose-rule/draft-management.ts (1)
  • stripQuotedContent (140-158)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: test
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Macroscope - Correctness Check
🔇 Additional comments (5)
apps/web/utils/similarity-score.ts (3)

6-17: LGTM on normalization helper.

The normalizeStoredContent function correctly replicates the isDraftUnmodified normalization pipeline: \n<br> conversion, HTML-to-text, then quote stripping with case normalization.


19-34: Normalization logic correctly handles Outlook HTML.

The conditional HTML-to-text conversion based on bodyContentType is the right approach—Outlook returns HTML in textPlain when bodyContentType === "html", while Gmail returns actual plain text.


44-69: Backwards-compatible signature with proper fallback handling.

The implementation correctly:

  • Maintains backwards compatibility with plain string inputs via parseReply
  • Falls back to textHtml when textPlain is empty (fixing HTML-only drafts)
  • Infers contentType as "html" when using textHtml fallback

Note: The legacy string path uses parseReply (EmailReplyParser) while the ParsedMessage path uses stripQuotedContent. These may handle edge cases differently, but this is appropriate for backwards compatibility.

apps/web/utils/reply-tracker/draft-tracking.ts (2)

82-83: Correct update to pass full message object.

Passing the full ParsedMessage enables proper HTML normalization for Outlook users, where bodyContentType === "html" indicates HTML content in textPlain.


167-173: Addresses HTML-only draft detection issue.

The updated condition draftDetails?.textPlain || draftDetails?.textHtml properly handles HTML-only drafts that previously would have been incorrectly treated as missing. Passing the full draftDetails object enables proper content-type-aware normalization.


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.

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Jan 4, 2026

Handle Outlook HTML in draft similarity scoring by updating apps/web/utils/similarity-score.calculateSimilarity and integrating full ParsedMessage flow in apps/web/utils/reply-tracker/draft-tracking.trackSentDraftStatus

Update similarity scoring to accept ParsedMessage, convert HTML to text, and strip quoted content; adjust draft tracking to pass full message objects and consider textPlain or textHtml; expand tests to cover HTML replies, quoted content, and plain-string compatibility. See similarity-score.ts and draft-tracking.ts.

📍Where to Start

Start with calculateSimilarity in similarity-score.ts, then review its usage in trackSentDraftStatus in draft-tracking.ts.


Macroscope summarized 2bf832a.

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.

No issues found across 3 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/utils/similarity-score.test.ts (2)

1-11: Add beforeEach to clear mocks between tests.

According to coding guidelines, tests should include beforeEach(() => { vi.clearAllMocks(); }) to ensure test isolation. While the current tests use the real implementation, adding this would ensure consistency with project standards and prevent issues if mocks are added later.

🔎 Suggested addition
 describe("calculateSimilarity - basic tests", () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
   it("should return 0.0 if either text is null or undefined", () => {

As per coding guidelines for **/*.test.{ts,tsx}.


115-137: Consider extracting createParsedMessage to shared test helpers.

The createParsedMessage helper is well-structured and could be useful for other test files that need to create mock ParsedMessage objects. Consider moving it to @/__tests__/helpers.ts for reusability across the codebase, especially if other tests need to work with ParsedMessage objects.

Based on learnings that recommend using existing helpers from @/__tests__/helpers.ts.

📜 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 a96ef31 and dfee416.

📒 Files selected for processing (3)
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.test.ts
  • apps/web/utils/similarity-score.ts
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Don't export imported variables
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{tsx,ts,css}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't 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
Use semantic elements instead of role attributes in JSX
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid AR...

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use @/ path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css}

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

Format code with Prettier

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
apps/web/**/*.{example,ts,json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Make sure the assertion function, like expect, is placed inside an it() function call
Don't use disabled tests

Files:

  • apps/web/utils/similarity-score.test.ts
apps/web/**/*.test.{ts,tsx}

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

Co-locate test files next to source files (e.g., utils/example.test.ts). Only E2E and AI tests go in __tests__/

Files:

  • apps/web/utils/similarity-score.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use vitest as the testing framework
Colocate test files next to the tested file with .test.ts or .test.tsx naming convention (e.g., dir/format.ts and dir/format.test.ts)
Mock server-only using vi.mock("server-only", () => ({}))
Mock Prisma using vi.mock("@/utils/prisma") and the provided mock from @/utils/__mocks__/prisma
Use test helper functions getEmail, getEmailAccount, and getRule from @/__tests__/helpers for creating mock data
Clear all mocks between tests using beforeEach(() => { vi.clearAllMocks(); })
Use descriptive test names that clearly indicate what is being tested
Do not mock the Logger in tests

Files:

  • apps/web/utils/similarity-score.test.ts
**/*.test.{js,jsx,ts,tsx}

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

Co-locate test files next to source files (e.g., utils/example.test.ts). Only E2E and AI tests go in __tests__/

Files:

  • apps/web/utils/similarity-score.test.ts
🧠 Learnings (13)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/reply-tracker/draft-tracking.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions

Applied to files:

  • apps/web/utils/similarity-score.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/similarity-score.ts
  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2026-01-01T10:42:29.775Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2026-01-01T10:42:29.775Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helper functions `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for creating mock data

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use vitest imports (`describe`, `expect`, `test`, `vi`, `beforeEach`) in LLM test files

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2026-01-01T10:42:29.775Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2026-01-01T10:42:29.775Z
Learning: Applies to **/__tests__/**/*.{ts,tsx} : Place AI tests in the `__tests__` directory and do not run them by default as they use a real LLM

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2026-01-01T10:42:29.775Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2026-01-01T10:42:29.775Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names that clearly indicate what is being tested

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/**/*.test.{ts,tsx} : Co-locate test files next to source files (e.g., `utils/example.test.ts`). Only E2E and AI tests go in `__tests__/`

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use `describe.runIf(isAiTest)` with environment variable `RUN_AI_TESTS === "true"` to conditionally run LLM tests

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use `console.debug()` for outputting generated LLM content in tests, e.g., `console.debug("Generated content:\n", result.content);`

Applied to files:

  • apps/web/utils/similarity-score.test.ts
📚 Learning: 2026-01-01T10:42:29.775Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2026-01-01T10:42:29.775Z
Learning: Applies to **/*.test.{ts,tsx} : Clear all mocks between tests using `beforeEach(() => { vi.clearAllMocks(); })`

Applied to files:

  • apps/web/utils/similarity-score.test.ts
🧬 Code graph analysis (3)
apps/web/utils/reply-tracker/draft-tracking.ts (1)
apps/web/utils/similarity-score.ts (1)
  • calculateSimilarity (47-78)
apps/web/utils/similarity-score.ts (3)
apps/web/utils/mail.ts (2)
  • convertEmailHtmlToText (105-126)
  • parseReply (9-13)
apps/web/utils/ai/choose-rule/draft-management.ts (1)
  • stripQuotedContent (140-158)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/similarity-score.test.ts (1)
apps/web/utils/similarity-score.ts (1)
  • calculateSimilarity (47-78)
⏰ 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). (4)
  • GitHub Check: Baz Reviewer
  • GitHub Check: Macroscope - Correctness Check
  • GitHub Check: test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
apps/web/utils/similarity-score.test.ts (3)

13-97: Excellent test coverage for basic scenarios.

The basic tests comprehensively cover edge cases including identical texts, case-insensitivity, whitespace normalization, empty inputs, and realistic email content. The test descriptions are clear and specific.


139-174: Strong integration tests for Outlook HTML content handling.

These tests directly address the core issue described in the PR objectives: handling Outlook's HTML content in draft similarity calculations. The scenarios cover plain text comparison with HTML responses, signature handling, and quoted content stripping—all critical for fixing the 0% match rate issue mentioned for Microsoft Outlook users.


176-242: Comprehensive coverage of Gmail, cross-platform scenarios, and backwards compatibility.

The integration tests effectively validate:

  • Gmail quoted content stripping
  • Cross-platform newline handling (\n vs \r\n)
  • Real-world sent email tracking scenarios
  • Backwards compatibility with plain string arguments

This ensures the refactoring doesn't break existing functionality while adding Outlook support.

apps/web/utils/similarity-score.ts (3)

13-20: LGTM: Correct normalization for stored content.

The normalization logic correctly implements the PR objective by converting newlines to <br> tags (matching stored format), converting to plain text, stripping quoted content, and normalizing case/whitespace. This aligns with how isDraftUnmodified handles content comparison.


26-37: LGTM: Correct Outlook HTML handling.

This function addresses the core issue described in the PR objectives. When bodyContentType === 'html' (Outlook case), it properly converts HTML to plain text before comparison. The normalization steps (strip quotes, lowercase, trim) are consistent with normalizeStoredContent, ensuring accurate similarity calculation.


47-78: LGTM: Well-structured refactoring with backwards compatibility.

The updated calculateSimilarity function correctly:

  • Maintains backwards compatibility by accepting both string and ParsedMessage as the second parameter
  • Uses the appropriate normalization path based on input type
  • Handles edge cases (null, undefined, empty strings) gracefully
  • Includes clear comments explaining the backwards compatibility approach

The implementation directly addresses the PR objectives by enabling proper Outlook HTML content handling while preserving existing Gmail functionality.

apps/web/utils/reply-tracker/draft-tracking.ts (2)

22-83: LGTM: Correct integration with updated calculateSimilarity.

The changes properly adapt to the refactored calculateSimilarity function:

  • Line 22: Added sentMessageId destructuring (used later for logging)
  • Line 83: Now passes the full message object instead of just textPlain, enabling proper Outlook HTML content handling via bodyContentType

The explanatory comment on line 82 clearly documents the purpose of passing the full message object.


169-173: LGTM: Consistent integration in draft cleanup flow.

The change to pass the full draftDetails object (instead of just textPlain) is consistent with the trackSentDraftStatus changes and enables proper Outlook HTML content handling during draft cleanup. The comment on line 169 clearly documents the purpose.

Comment on lines 65 to 70
// ParsedMessage - use proper normalization with bodyContentType
const text = providerMessage.textPlain || "";
normalized2 = normalizeProviderContent(
text,
providerMessage.bodyContentType,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

When textPlain is empty (common in Gmail/Outlook HTML-only replies), the similarity check compares against an empty string and never detects unmodified drafts. Should we fall back to textHtml?

Suggested change
// ParsedMessage - use proper normalization with bodyContentType
const text = providerMessage.textPlain || "";
normalized2 = normalizeProviderContent(
text,
providerMessage.bodyContentType,
);
const text = providerMessage.textPlain || providerMessage.textHtml || "";
const contentType = providerMessage.textPlain ? providerMessage.bodyContentType : "html";
normalized2 = normalizeProviderContent(
text,
contentType,
);

Finding types: AI Coding Guidelines Type Inconsistency

Copy link
Contributor

Choose a reason for hiding this comment

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

Commit 2bf832a addressed this comment. The code now falls back to providerMessage.textHtml when textPlain is empty, and dynamically sets the content type to "html" in such cases. This implementation matches the reviewer's suggestion and solves the issue where HTML-only emails would result in empty string comparisons.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good catch! Implemented the fallback to textHtml when textPlain is empty, treating it as HTML content.

- Remove unused extractDraftPlainText import
- Add fallback to textHtml when textPlain is empty (handles HTML-only emails)
- Update condition to check for textPlain OR textHtml
@elie222 elie222 merged commit 2a60b8d into main Jan 4, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant