Skip to content

[PB-1974]: feature/drafts#64

Merged
xabg2 merged 12 commits into
masterfrom
feature/drafts
Jun 23, 2026
Merged

[PB-1974]: feature/drafts#64
xabg2 merged 12 commits into
masterfrom
feature/drafts

Conversation

@xabg2

@xabg2 xabg2 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Implementing Drafted Emails. Adding the design + logic to Save a Draft, Update a Draft and Send a Draft and the flow after Sending a draft - removing the Draft and force-fetching the folders so we can get the "Sent" email.

@xabg2 xabg2 requested a review from jzunigax2 June 19, 2026 09:16
@xabg2 xabg2 self-assigned this Jun 19, 2026
@xabg2 xabg2 added the enhancement New feature or request label Jun 19, 2026
Base automatically changed from feature/forward to master June 19, 2026 13:53

const onSent = useCallback(async () => {
clearDialog();
}, [item, draftId, mode, deleteEmails, clearDialog]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

all of these dependencies are unused


const onSent = useCallback(async () => {
clearDialog();
}, [item, draftId, mode, deleteEmails, clearDialog]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also, deleteEmails seems unused aside from this

Comment on lines +9 to +15
export type PersistedAttachmentInput = {
blobId: string;
name: string;
size: number;
type: string;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this type is defined twice, also in useAttachments.ts

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying mail-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: b495fca
Status:🚫  Build failed.

View logs

@xabg2 xabg2 requested a review from jzunigax2 June 19, 2026 15:01
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@xabg2, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 7 minutes and 48 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c5bb9ba-ca36-4cb5-ab4d-77125f899d73

📥 Commits

Reviewing files that changed from the base of the PR and between 317fa3f and b495fca.

📒 Files selected for processing (1)
  • src/hooks/mail/useOpenDraftInCompose.ts
📝 Walkthrough

Walkthrough

Adds complete draft email support including new MailService methods and RTK Query mutations, a useDraftMessage autosave hook with encrypted payload building, persisted attachment hydration, draftId forwarding through the send pipeline, full ComposeMessageDialog wiring with session-key recovery, and draft-opening from MailView. Also refactors attachment rendering into a reusable AttachmentChip component and introduces file-extension-to-icon service infrastructure. Includes localization updates for draft error messages and UI strings, plus SDK version bump.

Changes

Draft Email Compose Feature

Layer / File(s) Summary
SDK methods and RTK Query draft mutations
src/errors/mail/index.ts, src/services/sdk/mail/index.ts, src/store/api/mail/index.ts, package.json
Adds DraftMessageError, three MailService draft methods (draftEmail, updateDraft, getDraft), and two RTK Query mutations (draftEmail, updateDraft) that invalidate ListFolder(drafts); narrows sendEmail invalidation tags to ListFolder entries only; exports useLazyGetThreadQuery and new draft hooks; bumps SDK version to ^1.17.10.
Draft mode initial state and PersistedAttachmentInput type
src/components/compose-message/hooks/useInitialComposeState.ts, src/components/compose-message/hooks/useInitialComposeState.test.ts
Exports PersistedAttachmentInput, extends InitialComposeData with draft-related fields (draftId, receivedAt, persistedAttachments), and implements draft-mode branch to populate all compose state from persisted draft; tests cover hydration with and without attachments, plus replyAll regression.
Persisted attachment support in useAttachments
src/components/compose-message/hooks/useAttachments.ts, src/components/compose-message/hooks/useAttachments.test.ts
Makes UploadedAttachment.file optional to support persisted attachments without local File objects; adds addPersistedAttachments callback enforcing total-bytes limit with warning toast on overflow and appending done-status entries with blobId without enqueueing uploads; tests cover happy path and size-limit rejection.
useDraftMessage autosave hook
src/components/compose-message/hooks/useDraftMessage.ts, src/components/compose-message/hooks/useDraftMessage.test.ts
Implements useDraftMessage with AUTOSAVE_DELAY_MS debounce, buildPayload assembling an encrypted DraftEmailRequest via MailEncryptionService with filtered done-attachments and mapped recipients, saveDraft routing to create or update, and handleDraftDiscard; tests cover encryption payload, update vs. create routing, missing-key skip, attachment filtering, and draft-id memory across saves.
useComposeSend draftId forwarding
src/components/compose-message/hooks/useComposeSend.ts, src/components/compose-message/hooks/useComposeSend.test.ts
Extends UseComposeSendParams with optional draftId, forwards it into the sendEmail request, and adds it to the send callback dependency array; tests verify sendEmail receives draftId correctly in both draft and non-draft cases, and onSent fires.
ComposeMessageDialog draft wiring and UI
src/components/compose-message/index.tsx
Generalizes body auto-fill to draft/forward modes via ALLOWED_MODES_TO_FILL_BODY, adds session-key state and one-time hydration guard, hydrates persisted attachments once via addPersistedAttachments, recovers draft attachment session key via decryption with MailKeysService/MailEncryptionService, wires useDraftMessage for autosave/draftId/discard state, awaits saveDraft on close (errors logged), passes draftId to useComposeSend with new onSent callback, disables close controls during isSending, and displays draft saved-at timestamp plus discard button.
Opening drafts from MailView
src/hooks/mail/useOpenDraftInCompose.ts, src/features/mail/MailView.tsx
Adds useOpenDraftInCompose hook that lazy-fetches the thread via useLazyGetThreadQuery, selects the matching draft message, and opens ActionDialog.ComposeMessage in draft mode; MailView.onSelectEmail early-returns for the drafts folder using this hook, bypassing normal selection and read-update flow.
Draft localization strings
src/i18n/locales/{en,es,fr,it}.json
Adds draft-related error messages (draftSaveFailed, draftDiscardFailed, draftOpenFailed) and saved-at UI string (savedAt) to all four locale files.

Attachment Component Refactoring

Layer / File(s) Summary
AttachmentChip component
src/components/attachment-chip/index.tsx
Introduces AttachmentChip with typed AttachmentChipStatus union (pending, uploading, done, error), status-to-icon rendering strategy (spinner for pending/uploading, warning for error, type-based for others), conditional retry/download/remove action buttons, and displays fileName and formatted size via bytesToString.
AttachmentList and mail-preview refactoring
src/components/compose-message/components/AttachmentList.tsx, src/features/mail/components/mail-preview/preview/index.tsx
Updates AttachmentList to replace inline icon rendering with AttachmentChip instances in a horizontally scrollable container; exports EmailAttachment type and updates mail-preview to use AttachmentChip for consistent attachment rendering with download support.

Icon Service for File Extensions

Layer / File(s) Summary
Extension group type definitions and mappings
src/services/icons/types/index.ts
Defines FileExtensionMap type, VideoExtensions and AudioExtensions interfaces, FileExtensionGroup enum, and internal maps for audio, video, code, figma, image, PDF, PPT, text, Word, Xls, Xml, Csv, and Zip extension categories; exports default fileExtensionGroups dictionary mapping each group to its extension list.
File extension service computation
src/services/icons/file-extension.service.ts, src/services/icons/file-extension.service.test.ts
Implements computeExtensionsLists and computeExtensionsList helper functions to build per-group extension mappings with optional filtering by a provided filter array; exports fileExtensionService with computeExtensionsLists method; tests validate default population, multi-extension flattening, and filtering behavior.
Icon service with extension resolution
src/services/icons/icon.service.ts, src/services/icons/icon.service.test.ts
Implements getItemIcon(isFolder, itemExtension?) that resolves file extensions to FileExtensionGroup via cached extension lists and returns corresponding SVG icon components or folder icon with case-insensitive matching; exports iconService with getItemIcon method; tests cover folder override, extension mappings, and unknown-extension fallback.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • larryrider
  • jzunigax2
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'PB-1974: feature/drafts' is related to the changeset but lacks specificity—it is a generic feature label without describing the actual implementation details of draft functionality. Consider using a more descriptive title that captures the primary feature (e.g., 'Add draft email save, update, and send functionality') to better communicate the changeset's scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description aligns well with the changeset, accurately describing the implementation of draft email functionality including saving, updating, sending, and post-send workflows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/drafts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
src/components/compose-message/index.tsx (1)

70-176: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract draft lifecycle orchestration into a dedicated hook.

This component now owns draft hydration, persisted attachment hydration, session-key recovery, autosave wiring, and close/send flow orchestration. That violates the repo’s “dumb component” rule and makes this UI path harder to test and evolve. Please move this logic into a useComposeDraftLifecycle-style hook and keep TSX render-focused.

As per coding guidelines, "Components must be dumb: move state/effects/data fetching/event handlers into use<Name> hooks; ensure logic is testable via hooks, not TSX components."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/index.tsx` around lines 70 - 176, Extract the
draft lifecycle management logic from the component into a new custom hook
called useComposeDraftLifecycle. This hook should encapsulate all the
draft-related useEffect hooks (the ones managing sessionKeyHydratedRef,
hydratedForwardAttachmentsRef, hydratedPersistedAttachmentsRef), the session-key
recovery logic, the clearDialog callback that manages ref resets, and the
onClose/onSent flow orchestration. Move all these effects, refs, and callbacks
into the hook so the component TSX only focuses on rendering and minimal state
management, returning the necessary callbacks and state from the hook for the
component to use.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/compose-message/hooks/useAttachments.ts`:
- Line 9: The import statement for PersistedAttachmentInput is using a relative
path './useInitialComposeState' instead of the repository path alias. Replace
the relative import path with the `@/`* alias format that maps to src/*, so the
import should reference the full path from src/ using the `@/` prefix. This
ensures consistency with the repository's coding guidelines for internal module
imports.

In `@src/components/compose-message/hooks/useComposeSend.ts`:
- Around line 236-237: Remove the draftId field from the payload object passed
to the sendEmail function in the useComposeSend hook, as draftId is not part of
the SendEmailRequestDto type defined in the SDK. The field should be removed
from the object that gets passed to the sendEmail call before the unwrap()
method is invoked. If draft tracking is required, implement that functionality
separately outside of the send request payload.

In `@src/components/compose-message/hooks/useDraftMessage.ts`:
- Around line 6-7: The import statements for Recipient and AttachmentTask in the
useDraftMessage.ts hook are using relative paths instead of the repository's
path alias convention. Replace the relative import paths (../types and
./useAttachments) with the corresponding `@/`* path aliases that map to the src
directory, following the repository's coding guidelines for internal module
imports. Update both the import from '../types' and the import from
'./useAttachments' to use their absolute alias paths from the src root.
- Around line 65-72: The buildPayload function returns an encryption field that
is not defined in the DraftEmailRequest type from the SDK, creating a type
mismatch that could cause runtime validation failures. Remove the encryption
field from the return object in buildPayload, or verify with SDK maintainers
that encryption is a supported field and update the type definitions
accordingly. Additionally, update the relative import statement for Recipient
type from '../types' to use the path alias '`@/components/compose-message/types`'
instead.

In `@src/components/compose-message/index.tsx`:
- Around line 117-128: The sessionKeyHydratedRef.current flag is being set to
true before the decryption promise completes, which means if the
decryptAttachmentsSessionKey call fails, the flag remains true and subsequent
retries never happen. Move the sessionKeyHydratedRef.current = true assignment
into the .then() success callback after setAttachmentsSessionKey is called, or
alternatively reset the flag to false in the .catch() error handler to allow
future retry attempts when the effect dependencies change again.
- Around line 153-156: The onClose function in the compose-message component
silently catches errors from saveDraft() and still clears the dialog, causing
potential silent draft loss. Replace the catch block with proper error handling
that notifies the user using ErrorService.instance.notifyUser() or
notificationsService.show() with a translated error message following the
pattern translate('errors.<domain>.<key>'), and ensure clearDialog() is only
called when saveDraft() succeeds. Keep the dialog open if the save operation
fails to prevent losing unsaved changes.

In `@src/hooks/mail/useOpenDraftInCompose.ts`:
- Around line 10-13: The draft lookup in the async callback is falling back to
thread[0] when the draft ID is not found, which can mask when a draft is deleted
or moved. Modify the line that assigns the draft variable (currently using
`thread.find((m) => m.id === draftId) ?? thread[0]`) by either removing the
fallback operator and thread[0] reference entirely to rely solely on the
subsequent !draft guard check, or alternatively keep the fallback but add a
console warning or logger call when the fallback is used to indicate that the
intended draft was not found and a fallback message is being opened instead.

In `@src/services/sdk/mail/index.ts`:
- Line 16: The import of UpdateDraftResponse on line 16 does not exist in the
current SDK version 1.17.7, causing a compilation error. Either upgrade the
`@internxt/sdk` dependency to a version that exports UpdateDraftResponse, or
remove this import and refactor the updateDraft() and getDraft() methods to use
return types and SDK method calls that are actually available in version 1.17.7.
Verify that all SDK method calls (this.client.updateDraft(),
this.client.getDraft()) correspond to actual methods exported by the installed
SDK version.

In `@src/store/api/mail/index.ts`:
- Around line 326-327: The sendEmail cache invalidation is too broad and
invalidates all ListFolder caches when only the sent and drafts folders should
be refreshed. Modify the invalidatesTags function in the sendEmail endpoint to
return a more targeted array that includes ListFolder invalidations only for the
specific folder IDs 'sent' (to display the newly sent email) and 'drafts' (to
remove the sent draft), rather than invalidating the entire ListFolder type
without an ID, which forces unnecessary refetches of all folders.
- Line 34: The import statement references `UpdateDraftResponse` which does not
exist in the SDK (v1.17.7), and the corresponding `updateDraft()` method call
does not exist on the MailApi either. Replace the `UpdateDraftResponse` import
with `EmailCreatedResponse` which is the actual type exported by the SDK, and
update the method call from `updateDraft()` to `saveDraft()` throughout the file
(including in src/services/sdk/mail/index.ts at line 160 where the SDK client
method is called). This aligns the code with the actual SDK API that exports
saveDraft() returning EmailCreatedResponse.

---

Outside diff comments:
In `@src/components/compose-message/index.tsx`:
- Around line 70-176: Extract the draft lifecycle management logic from the
component into a new custom hook called useComposeDraftLifecycle. This hook
should encapsulate all the draft-related useEffect hooks (the ones managing
sessionKeyHydratedRef, hydratedForwardAttachmentsRef,
hydratedPersistedAttachmentsRef), the session-key recovery logic, the
clearDialog callback that manages ref resets, and the onClose/onSent flow
orchestration. Move all these effects, refs, and callbacks into the hook so the
component TSX only focuses on rendering and minimal state management, returning
the necessary callbacks and state from the hook for the component to use.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d558dbc3-3e31-47aa-8c2e-9b337fedb42a

📥 Commits

Reviewing files that changed from the base of the PR and between 710be09 and 5f774b6.

📒 Files selected for processing (14)
  • src/components/compose-message/hooks/useAttachments.test.ts
  • src/components/compose-message/hooks/useAttachments.ts
  • src/components/compose-message/hooks/useComposeSend.test.ts
  • src/components/compose-message/hooks/useComposeSend.ts
  • src/components/compose-message/hooks/useDraftMessage.test.ts
  • src/components/compose-message/hooks/useDraftMessage.ts
  • src/components/compose-message/hooks/useInitialComposeState.test.ts
  • src/components/compose-message/hooks/useInitialComposeState.ts
  • src/components/compose-message/index.tsx
  • src/errors/mail/index.ts
  • src/features/mail/MailView.tsx
  • src/hooks/mail/useOpenDraftInCompose.ts
  • src/services/sdk/mail/index.ts
  • src/store/api/mail/index.ts

Comment thread src/components/compose-message/hooks/useAttachments.ts
Comment on lines +236 to 237
draftId,
}).unwrap();

Copy link
Copy Markdown

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

🏁 Script executed:

fd -e ts -e tsx "useComposeSend" --type f

Repository: internxt/mail-web

Length of output: 176


🏁 Script executed:

rg "SendEmailRequest" --type ts -A 5 -B 2

Repository: internxt/mail-web

Length of output: 3591


🏁 Script executed:

cat -n src/components/compose-message/hooks/useComposeSend.ts | sed -n '230,245p'

Repository: internxt/mail-web

Length of output: 654


🏁 Script executed:

fd -path "*/node_modules/@internxt/sdk*" "types*" --type f 2>/dev/null | head -20

Repository: internxt/mail-web

Length of output: 43


🏁 Script executed:

rg "type SendEmailRequest|interface SendEmailRequest" --type ts -A 15

Repository: internxt/mail-web

Length of output: 43


🏁 Script executed:

find . -path "*/node_modules/@internxt/sdk/dist/mail*" -name "*.d.ts" 2>/dev/null | head -10

Repository: internxt/mail-web

Length of output: 212


🏁 Script executed:

cat ./node_modules/@internxt/sdk/dist/mail/types.d.ts | grep -A 25 "SendEmailRequest"

Repository: internxt/mail-web

Length of output: 2133


🏁 Script executed:

cat ./node_modules/@internxt/sdk/dist/mail/schema.d.ts | grep -A 30 "SendEmailRequestDto"

Repository: internxt/mail-web

Length of output: 2251


Remove draftId from the sendEmail payload.

The draftId field is not part of the SendEmailRequest type (defined as SendEmailRequestDto in the SDK). Passing it will cause a TypeScript compilation error. If draft tracking is needed, this should be handled separately outside the send request payload.

🧰 Tools
🪛 GitHub Actions: Mail Web CI / 0_ci (24.x).txt

[error] 236-236: TypeScript error TS2353: Object literal may only specify known properties, and 'draftId' does not exist in type '{ to: { name?: string | undefined; email: string; }[]; cc?: { name?: string | undefined; email: string; }[] | undefined; bcc?: { name?: string | undefined; email: string; }[] | undefined; subject: string; ... 5 more ...; inReplyToEmailId?: string | undefined; }'.

🪛 GitHub Actions: Mail Web CI / ci (24.x)

[error] 236-236: TypeScript (TS2353) build failed. Object literal may only specify known properties; 'draftId' does not exist in the expected type for the object passed in this location.

🪛 GitHub Check: ci (24.x)

[failure] 236-236:
Object literal may only specify known properties, and 'draftId' does not exist in type '{ to: { name?: string | undefined; email: string; }[]; cc?: { name?: string | undefined; email: string; }[] | undefined; bcc?: { name?: string | undefined; email: string; }[] | undefined; subject: string; ... 5 more ...; inReplyToEmailId?: string | undefined; }'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/hooks/useComposeSend.ts` around lines 236 -
237, Remove the draftId field from the payload object passed to the sendEmail
function in the useComposeSend hook, as draftId is not part of the
SendEmailRequestDto type defined in the SDK. The field should be removed from
the object that gets passed to the sendEmail call before the unwrap() method is
invoked. If draft tracking is required, implement that functionality separately
outside of the send request payload.

Source: Linters/SAST tools

Comment thread src/components/compose-message/hooks/useDraftMessage.ts
Comment on lines +65 to +72
return {
to: toRecipients.length ? toRecipients.map(toEmailAddress) : undefined,
cc: ccRecipients.length ? ccRecipients.map(toEmailAddress) : undefined,
bcc: bccRecipients.length ? bccRecipients.map(toEmailAddress) : undefined,
subject: subject || undefined,
encryption,
attachments: doneAttachments.length ? doneAttachments : undefined,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/compose-message/hooks/useDraftMessage.ts

Repository: internxt/mail-web

Length of output: 4895


🏁 Script executed:

find . -type f -name "*.d.ts" -o -name "*.ts" | xargs grep -l "DraftEmailRequest" | head -20

Repository: internxt/mail-web

Length of output: 333


🏁 Script executed:

rg "DraftEmailRequest" --type ts --type tsx -A 5 -B 2

Repository: internxt/mail-web

Length of output: 89


🏁 Script executed:

rg "DraftEmailRequest" -A 8 -B 2

Repository: internxt/mail-web

Length of output: 6873


🏁 Script executed:

cat node_modules/@internxt/sdk/dist/mail/types.d.ts | grep -A 15 "DraftEmailRequest"

Repository: internxt/mail-web

Length of output: 1174


🏁 Script executed:

cat node_modules/@internxt/sdk/dist/mail/schema.d.ts | grep -A 25 "DraftEmailRequestDto"

Repository: internxt/mail-web

Length of output: 1799


🏁 Script executed:

npm run lint 2>&1 | grep -i "encryption\|draft" | head -30

Repository: internxt/mail-web

Length of output: 118


🏁 Script executed:

npx tsc --noEmit 2>&1 | grep -i "encryption\|useDraftMessage" | head -50

Repository: internxt/mail-web

Length of output: 43


🏁 Script executed:

cat node_modules/@internxt/sdk/dist/mail/types.d.ts | head -100

Repository: internxt/mail-web

Length of output: 3754


🏁 Script executed:

head -10 src/components/compose-message/types.ts

Repository: internxt/mail-web

Length of output: 160


🏁 Script executed:

head -10 src/components/compose-message/hooks/useAttachments.ts

Repository: internxt/mail-web

Length of output: 626


🏁 Script executed:

ls -la src/components/compose-message/

Repository: internxt/mail-web

Length of output: 587


🏁 Script executed:

rg "`@internxt/sdk`" node_modules/@internxt/sdk/package.json -A 3

Repository: internxt/mail-web

Length of output: 224


🏁 Script executed:

cat src/tsconfig.json 2>/dev/null || cat tsconfig.json | grep -A 20 "compilerOptions"

Repository: internxt/mail-web

Length of output: 43


🏁 Script executed:

cat src/components/compose-message/types/index.ts 2>/dev/null || cat src/components/compose-message/types.ts 2>/dev/null || find src/components/compose-message -name "*.ts" -o -name "*.tsx" | xargs ls -la

Repository: internxt/mail-web

Length of output: 436


🏁 Script executed:

grep -n "import.*\.\." src/components/compose-message/hooks/useDraftMessage.ts

Repository: internxt/mail-web

Length of output: 106


🏁 Script executed:

cat node_modules/@internxt/sdk/dist/mail/schema.d.ts | grep -B 5 -A 30 "DraftEmailRequestDto:"

Repository: internxt/mail-web

Length of output: 1303


🏁 Script executed:

cat tsconfig.json | jq '.compilerOptions | {skipLibCheck, strict, noImplicitAny}' 2>/dev/null || grep -A 30 "compilerOptions" tsconfig.json

Repository: internxt/mail-web

Length of output: 131


🏁 Script executed:

cat src/components/compose-message/hooks/useDraftMessage.ts | sed -n '49,73p'

Repository: internxt/mail-web

Length of output: 1274


🏁 Script executed:

rg "buildEncryptionBlock" -A 5 -B 2 --max-count=3

Repository: internxt/mail-web

Length of output: 8961


Payload includes encryption field not present in SDK DraftEmailRequest type.

The buildPayload function returns an object with an encryption field (line 70), but DraftEmailRequest from @internxt/sdk only defines: to, cc, bcc, subject, textBody, htmlBody, and attachments. TypeScript currently does not catch this as an error, but the payload schema is misaligned with the SDK contract and will likely cause runtime validation failures on the server.

Fix: Either remove encryption from the draft payload (if drafts should not store encryption metadata), or verify with the SDK maintainers that encryption is a supported field and update the type definitions accordingly.

Minor: Use path alias for relative import on line 6.

Replace import type { Recipient } from '../types' with import type { Recipient } from '@/components/compose-message/types'.

🧰 Tools
🪛 GitHub Check: ci (24.x)

[failure] 70-70:
Object literal may only specify known properties, and 'encryption' does not exist in type '{ to?: { name?: string | undefined; email: string; }[] | undefined; cc?: { name?: string | undefined; email: string; }[] | undefined; bcc?: { name?: string | undefined; email: string; }[] | undefined; subject?: string | undefined; textBody?: string | undefined; htmlBody?: string | undefined; attachments?: { ...; }[]...'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/hooks/useDraftMessage.ts` around lines 65 -
72, The buildPayload function returns an encryption field that is not defined in
the DraftEmailRequest type from the SDK, creating a type mismatch that could
cause runtime validation failures. Remove the encryption field from the return
object in buildPayload, or verify with SDK maintainers that encryption is a
supported field and update the type definitions accordingly. Additionally,
update the relative import statement for Recipient type from '../types' to use
the path alias '`@/components/compose-message/types`' instead.

Source: Linters/SAST tools

Comment thread src/components/compose-message/index.tsx
Comment thread src/components/compose-message/index.tsx Outdated
Comment thread src/hooks/mail/useOpenDraftInCompose.ts Outdated
Comment thread src/services/sdk/mail/index.ts Outdated
Comment thread src/store/api/mail/index.ts Outdated
Comment thread src/store/api/mail/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (1)
src/components/compose-message/hooks/useDraftMessage.ts (1)

102-113: ⚠️ Potential issue | 🟠 Major

Autosave does not trigger on editor body changes.

The editor in the dependency array is a stable TipTap Editor instance whose reference never changes. When users type in the message body, no dependency changes occur, so the autosave effect won't re-run until recipients, subject, or attachments change.

TipTap's editor.on('transaction') event is available and already used elsewhere in the codebase (e.g., useEditorBar.ts). Subscribe to this event to track body changes and trigger autosave on edit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/hooks/useDraftMessage.ts` around lines 102 -
113, The autosave effect does not trigger when editor body changes because the
`editor` reference in the dependency array is stable and never changes when
content is edited. Within the useEffect that sets up the autosave timer,
subscribe to the TipTap editor's `editor.on('transaction')` event to detect when
the editor content changes, and trigger the autosave timer reset on each
transaction. Remove `editor` from the dependency array and instead rely on the
transaction event subscription to drive autosave behavior, similar to how it is
implemented in `useEditorBar.ts`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/attachment-chip/index.tsx`:
- Around line 41-55: The button elements in this component are missing the
explicit type attribute, which causes them to default to type="submit" when used
within a form context, potentially triggering unintended form submissions. Add
type="button" to all button elements in this component, specifically the retry
upload button with onClick={onRetry} and the download attachment button with
onClick={onDownload}, as well as any other buttons referenced in the "Also
applies to" section (lines 69-72), to explicitly prevent form submission
behavior.
- Around line 42-43: The aria-label attributes in the attachment-chip component
contain hardcoded English strings like "Retry upload", "Download attachment",
and "Remove attachment" instead of using i18n translations. Replace each
hardcoded aria-label string with a call to translate() using appropriate i18n
keys. Create new i18n key entries in the locale files (src/i18n/locales/en.json,
es.json, fr.json, it.json) for each aria-label string, then update the
aria-label attributes to reference these keys through the translate function.
- Around line 68-72: The remove button in the attachment-chip component is only
visible on hover due to the className containing `hidden group-hover:flex`,
making it inaccessible to keyboard users. Modify the className of the button
element to be visible not only on group-hover but also on focus-visible or
focus-within states so that keyboard navigation can reach and interact with the
remove control. This ensures the button is discoverable for both pointer and
keyboard users while maintaining the hover behavior for mouse users.

In `@src/services/icons/file-extension.service.ts`:
- Around line 1-31: Create a test file named file-extension.service.test.ts in
the same directory as file-extension.service.ts to provide test coverage for the
service logic. The test file should follow the AAA (Arrange-Act-Assert) pattern
and cover multiple scenarios: testing computeExtensionsLists with no filter
argument (happy path), testing computeExtensionsLists with a filter applied,
testing computeExtensionsList function directly with various filter conditions,
testing edge cases such as empty filters and filtering that excludes all
extensions, and verifying that the FileExtensionGroup enum is iterated correctly
and all groups are processed. Include assertions to verify that the computed
extension lists contain the correct extensions and that filtering works as
expected.
- Around line 27-31: The fileExtensionService is currently exported as a plain
object instead of following the singleton pattern required per AGENTS.md.
Convert the service to a class named FileExtensionService that contains the
computeExtensionsLists method, add a static readonly instance property that
instantiates the class, and change the export to export the FileExtensionService
class instead of the fileExtensionService object. Update all callers to use
FileExtensionService.instance.computeExtensionsLists() instead of
fileExtensionService.computeExtensionsLists().
- Around line 3-18: Add JSDoc documentation to the public computeExtensionsLists
function to comply with AGENTS.md guidelines. Create a JSDoc block above the
function that describes what the function does (computing extension lists for
different file extension groups), explains the filter parameter (its type as an
object mapping FileExtensionGroup to string arrays, and its purpose/default
value), and documents the return type and what the returned Record contains.
Ensure the documentation clarifies the purpose of the function and how callers
should use it.
- Around line 8-14: The numeric enum FileExtensionGroup creates dual key mapping
which causes TypeScript type errors when indexing with string values after
filtering. Replace Object.values() with Object.keys() to iterate directly over
the enum keys, then cast each key using as keyof typeof FileExtensionGroup
before indexing into the enum. This allows you to safely extract the enum value
and use it for subsequent indexing operations in the extensionsLists and filter
assignments, maintaining proper type safety.

In `@src/services/icons/icon.service.ts`:
- Line 48: Fix the TypeScript enum indexing error at the assignment `groupId =
FileExtensionGroup[key]` by applying a type cast or refactoring approach (use
the same solution as applied in file-extension.service.ts for the
FileExtensionGroup reverse-key issue). Additionally, add a clarifying comment
above the line `const XmlFile = CodeFile;` explaining the design choice for why
XmlFile is aliased to CodeFile rather than having its own icon definition.
- Around line 1-62: Create a new test file icon.service.test.ts that tests the
icon selection logic in the getItemIcon function. The tests should follow the
AAA (Arrange, Act, Assert) pattern and cover: all FileExtensionGroup entries by
verifying that each extension group returns the correct icon component from
iconsByFileExtensionGroup, folder handling by confirming that isFolder true
returns LightFolder regardless of extension, edge cases including undefined
extension (should return DefaultFile), non-matching extensions (should return
DefaultFile), and case-insensitive extension matching (uppercase and mixed case
should match). Additionally, verify consistency with the file-extension.service
by testing that the extension matching logic works correctly with the
extensionsList computed from fileExtensionService.computeExtensionsLists().
- Around line 58-62: The iconService is currently exported as a plain object
instead of following the required singleton pattern. Refactor by creating an
IconService class with the getItemIcon method as an instance method, add a
static readonly instance property that instantiates the class, and update the
default export to export the IconService class (or the instance). This ensures
consistency with the singleton pattern required by AGENTS.md and allows callers
to use either IconService.instance.getItemIcon or import the instance directly
depending on how other parts of the codebase are configured.
- Around line 40-56: Add JSDoc documentation to the getItemIcon function to
clarify that the itemExtension parameter receives MIME type subparts (e.g.,
'svg+xml' from 'image/svg+xml', 'plain' from 'text/plain') rather than
traditional file extensions. Document that not all MIME type subparts map
cleanly to entries in the extensionsList (such as 'plain' not matching text
extensions or 'svg+xml' not matching 'svg'), and explicitly note that when no
matching extension group is found, the function falls back to
FileExtensionGroup.Default and returns the corresponding icon.
- Around line 44-52: The extension-matching logic in the loop over
Object.entries(extensionsList) uses .every() with side effects and inverted
logic (return !matched), which is non-idiomatic. Replace this with .find()
instead, which will return the first matching entry where
list.includes(itemExtension.toLowerCase()) is true. Once a match is found via
.find(), extract the key from that entry and set groupId =
FileExtensionGroup[key]. This approach is clearer and more semantically correct
for finding a single matching extension group rather than checking all elements.

In `@src/services/icons/types/index.ts`:
- Line 117: The extension key 'ogg' is mapped in both audioExtensions and
videoExtensions object mappings, which causes non-deterministic icon selection
since Object.entries iteration order is not guaranteed. Remove the duplicate
'ogg' key from one of the mappings (either audioExtensions or videoExtensions)
and rename it to be unique (e.g., 'ogg_audio' or 'ogg_video') to disambiguate
which format the extension maps to, ensuring consistent and predictable icon
selection based on the file type context.

---

Outside diff comments:
In `@src/components/compose-message/hooks/useDraftMessage.ts`:
- Around line 102-113: The autosave effect does not trigger when editor body
changes because the `editor` reference in the dependency array is stable and
never changes when content is edited. Within the useEffect that sets up the
autosave timer, subscribe to the TipTap editor's `editor.on('transaction')`
event to detect when the editor content changes, and trigger the autosave timer
reset on each transaction. Remove `editor` from the dependency array and instead
rely on the transaction event subscription to drive autosave behavior, similar
to how it is implemented in `useEditorBar.ts`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da4e26ba-a839-4745-84d8-406c500fdb10

📥 Commits

Reviewing files that changed from the base of the PR and between 5f774b6 and 53ec2f2.

⛔ Files ignored due to path filters (20)
  • src/assets/icons/file-types/audio.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/code.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/csv.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/default.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/drive.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/excel.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/figma.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/folder.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/illustrator.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/image.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/indesign.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/pdf.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/photoshop.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/powerpoint.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/ppt.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/sketch.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/txt.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/video.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/word.svg is excluded by !**/*.svg
  • src/assets/icons/file-types/zip.svg is excluded by !**/*.svg
📒 Files selected for processing (8)
  • src/components/attachment-chip/index.tsx
  • src/components/compose-message/components/AttachmentList.tsx
  • src/components/compose-message/hooks/useDraftMessage.ts
  • src/components/compose-message/index.tsx
  • src/features/mail/components/mail-preview/preview/index.tsx
  • src/services/icons/file-extension.service.ts
  • src/services/icons/icon.service.ts
  • src/services/icons/types/index.ts

Comment thread src/components/attachment-chip/index.tsx
Comment thread src/components/attachment-chip/index.tsx
Comment thread src/components/attachment-chip/index.tsx
Comment thread src/services/icons/file-extension.service.ts
Comment thread src/services/icons/file-extension.service.ts
Comment thread src/services/icons/icon.service.ts
Comment thread src/services/icons/icon.service.ts
Comment thread src/services/icons/icon.service.ts Outdated
Comment thread src/services/icons/icon.service.ts
Comment thread src/services/icons/types/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (3)
src/store/api/mail/index.ts (1)

343-354: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid refetching drafts list on every autosave update.

updateDraft currently invalidates ListFolder:drafts on each call. With debounced autosave, this can cause repeated background refetch churn and unnecessary load.

Suggested change
     updateDraft: builder.mutation<EmailResponse, { draftId: string; payload: DraftEmailRequest }>({
@@
-      invalidatesTags: [{ type: 'ListFolder', id: 'drafts' }],
+      // Autosave path: avoid forcing a drafts refetch on every keystroke-triggered update.
+      invalidatesTags: [],
     }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/store/api/mail/index.ts` around lines 343 - 354, The updateDraft mutation
is invalidating the ListFolder:drafts tag on every call, which causes
unnecessary refetch churn when autosave is debounced and triggers multiple
updates. Remove the invalidatesTags array from the updateDraft mutation
definition to prevent automatic invalidation of the drafts list on each draft
update, since updating an individual draft does not require refreshing the
entire drafts folder list.
src/hooks/mail/useOpenDraftInCompose.ts (1)

5-21: ⚠️ Potential issue | 🟠 Major

Add co-located tests for this logic hook.

This hook contains async logic and side effects but lacks a paired useOpenDraftInCompose.test.ts. Add test coverage following AAA pattern, including success path (thread found, dialog opened) and missing-draft path (returns early), with vi.restoreAllMocks() in beforeEach.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/mail/useOpenDraftInCompose.ts` around lines 5 - 21, Create a new
test file `useOpenDraftInCompose.test.ts` co-located with the hook to add test
coverage for the async logic and side effects. Add tests following the AAA
pattern that cover the success path (thread fetched, draft found, and openDialog
called with correct parameters including ActionDialog.ComposeMessage and draft
data) and the missing-draft path (when thread is fetched but draft is not found,
function returns early without calling openDialog). Mock the dependencies
useLazyGetThreadQuery and useActionDialog hooks, and include
vi.restoreAllMocks() in a beforeEach hook to clean up mocks between test cases.

Source: Coding guidelines

src/components/compose-message/index.tsx (1)

117-124: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move draft orchestration out of the TSX component into a hook.

This block adds state/effects/event handlers directly in the component, which increases coupling and makes the dialog non-dumb. Extract the draft lifecycle (hydrate persisted attachments, recover session key, save/close/discard handlers) into a dedicated hook and pass only props/callbacks to JSX.

♻️ Suggested direction
- const hydratedPersistedAttachmentsRef = useRef(false);
- useEffect(() => { ... }, [mode, item.persistedAttachments, addPersistedAttachments]);
- useEffect(() => { ...decryptAttachmentsSessionKey... }, [mode, composeDialogData]);
- const onClose = useCallback(async () => { ... }, [...]);
- const onDiscardDraft = useCallback(async () => { ... }, [...]);
+ const {
+   onClose,
+   onDiscardDraft,
+   draftSavedAt,
+   isDiscarding,
+ } = useComposeDraftLifecycle({
+   mode,
+   item,
+   composeDialogData,
+   attachments,
+   recipients: { toRecipients, ccRecipients, bccRecipients },
+   subject: subjectValue,
+   editor,
+   closeDialog,
+   attachmentsSessionKey,
+   setAttachmentsSessionKey,
+ });

As per coding guidelines, src/{features,components}/**/*.tsx components must be dumb and render-only, with logic extracted to hooks.

Also applies to: 126-143, 158-189

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/index.tsx` around lines 117 - 124, Extract the
draft lifecycle management logic from the compose-message component into a
dedicated custom hook. Move the useEffect block that handles persisted
attachments hydration (lines 117-124), along with the session key recovery logic
(lines 126-143) and the save/close/discard event handlers (lines 158-189) into a
new hook (e.g., useDraftLifecycle or useDraftOrchestration). This hook should
manage all state updates and side effects internally and return only the
necessary callbacks and values needed by the component JSX. Replace the direct
state declarations and useEffect calls in the TSX with a single call to this new
hook, keeping the component focused solely on rendering UI based on the props
and callbacks provided by the hook.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/compose-message/index.tsx`:
- Around line 301-303: The discard draft button (with onDiscardDraft handler) at
line 301 is only disabled when isDiscarding is true, but it should also be
disabled while a send operation is in progress to prevent concurrent mutations
on the same draft. Add an additional condition to the disabled prop of the
Button with variant="destructive" to disable it when the send operation is
active (likely controlled by a state variable like isSending based on the
similar pattern at line 329). This ensures the discard action cannot be
triggered while the draft is being sent.
- Line 297: The className attribute in the flex container div contains an
invalid Tailwind utility class token with a space: "items center". Replace this
with the correct hyphenated class name "items-center" to properly enable
vertical centering in the flexbox layout. The fix involves changing the
className string from "flex flex-row justify-between items center" to include
the correct "items-center" token.

---

Outside diff comments:
In `@src/components/compose-message/index.tsx`:
- Around line 117-124: Extract the draft lifecycle management logic from the
compose-message component into a dedicated custom hook. Move the useEffect block
that handles persisted attachments hydration (lines 117-124), along with the
session key recovery logic (lines 126-143) and the save/close/discard event
handlers (lines 158-189) into a new hook (e.g., useDraftLifecycle or
useDraftOrchestration). This hook should manage all state updates and side
effects internally and return only the necessary callbacks and values needed by
the component JSX. Replace the direct state declarations and useEffect calls in
the TSX with a single call to this new hook, keeping the component focused
solely on rendering UI based on the props and callbacks provided by the hook.

In `@src/hooks/mail/useOpenDraftInCompose.ts`:
- Around line 5-21: Create a new test file `useOpenDraftInCompose.test.ts`
co-located with the hook to add test coverage for the async logic and side
effects. Add tests following the AAA pattern that cover the success path (thread
fetched, draft found, and openDialog called with correct parameters including
ActionDialog.ComposeMessage and draft data) and the missing-draft path (when
thread is fetched but draft is not found, function returns early without calling
openDialog). Mock the dependencies useLazyGetThreadQuery and useActionDialog
hooks, and include vi.restoreAllMocks() in a beforeEach hook to clean up mocks
between test cases.

In `@src/store/api/mail/index.ts`:
- Around line 343-354: The updateDraft mutation is invalidating the
ListFolder:drafts tag on every call, which causes unnecessary refetch churn when
autosave is debounced and triggers multiple updates. Remove the invalidatesTags
array from the updateDraft mutation definition to prevent automatic invalidation
of the drafts list on each draft update, since updating an individual draft does
not require refreshing the entire drafts folder list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d88575a4-870b-410c-8d3e-283279687f9e

📥 Commits

Reviewing files that changed from the base of the PR and between 53ec2f2 and e9c564a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • package.json
  • src/components/compose-message/hooks/useDraftMessage.test.ts
  • src/components/compose-message/hooks/useDraftMessage.ts
  • src/components/compose-message/hooks/useInitialComposeState.ts
  • src/components/compose-message/index.tsx
  • src/hooks/mail/useOpenDraftInCompose.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
  • src/services/sdk/mail/index.ts
  • src/store/api/mail/index.ts

Comment thread src/components/compose-message/index.tsx Outdated
Comment thread src/components/compose-message/index.tsx Outdated
Comment thread src/components/compose-message/index.tsx Outdated
Comment thread src/hooks/mail/useOpenDraftInCompose.ts Outdated
Comment thread src/components/compose-message/index.tsx
@xabg2 xabg2 requested a review from jzunigax2 June 23, 2026 10:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/components/compose-message/index.tsx (1)

126-142: 🗄️ Data Integrity & Integration | 🟠 Major

Add keys to effect dependencies or use a reactive hook to ensure session-key recovery retries when keys become available.

At line 130, if getCurrentKeys() returns null when the effect first runs, the effect exits and never retries. Since the effect depends only on [mode, composeDialogData], if keys load into MailKeysService later, the effect won't re-run. This leaves draft attachments with an incorrect attachmentsSessionKey, mismatched from the persisted encryption context.

Either depend the effect on a reactive key source (e.g., extract to a hook or observe MailKeysService state), or add a guard at the dialog level to prevent opening before keys are decrypted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/compose-message/index.tsx` around lines 126 - 142, The
useEffect hook that calls
MailEncryptionService.instance.decryptAttachmentsSessionKey exits early if
MailKeysService.instance.getCurrentKeys() returns null on the first run, and
never retries since the dependency array only includes mode and
composeDialogData. To fix this, either extract a reactive hook or mechanism to
observe MailKeysService key availability and add that as a dependency to the
useEffect so it re-runs when keys become available, or add a guard at the
dialog/compose level to prevent opening the compose dialog before keys are
loaded. This ensures sessionKeyHydratedRef and attachmentsSessionKey are
properly set even if keys load after the component mounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/mail/useOpenDraftInCompose.ts`:
- Around line 16-17: When the draft is not found in the fetched thread (in the
`if (!draft) return;` block), the function returns without notifying the user.
Before the return statement, add a user feedback notification using the same
translated open-failure message that is used for error cases in this hook. This
ensures the user is informed when their requested draft cannot be opened rather
than experiencing silent failure.

---

Outside diff comments:
In `@src/components/compose-message/index.tsx`:
- Around line 126-142: The useEffect hook that calls
MailEncryptionService.instance.decryptAttachmentsSessionKey exits early if
MailKeysService.instance.getCurrentKeys() returns null on the first run, and
never retries since the dependency array only includes mode and
composeDialogData. To fix this, either extract a reactive hook or mechanism to
observe MailKeysService key availability and add that as a dependency to the
useEffect so it re-runs when keys become available, or add a guard at the
dialog/compose level to prevent opening the compose dialog before keys are
loaded. This ensures sessionKeyHydratedRef and attachmentsSessionKey are
properly set even if keys load after the component mounts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ff69f26-a590-466c-99d0-209b8ab8befe

📥 Commits

Reviewing files that changed from the base of the PR and between e9c564a and 317fa3f.

📒 Files selected for processing (13)
  • src/components/compose-message/components/AttachmentList.tsx
  • src/components/compose-message/hooks/useDraftMessage.test.ts
  • src/components/compose-message/hooks/useDraftMessage.ts
  • src/components/compose-message/index.tsx
  • src/hooks/mail/useOpenDraftInCompose.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
  • src/services/icons/file-extension.service.test.ts
  • src/services/icons/file-extension.service.ts
  • src/services/icons/icon.service.test.ts
  • src/services/icons/icon.service.ts

Comment thread src/hooks/mail/useOpenDraftInCompose.ts Outdated
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
73.5% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@xabg2 xabg2 merged commit 6c34322 into master Jun 23, 2026
3 of 6 checks passed
@xabg2 xabg2 deleted the feature/drafts branch June 23, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants