Skip to content

[PB-1969]: feat/forward a message#63

Merged
xabg2 merged 6 commits into
masterfrom
feature/forward
Jun 19, 2026
Merged

[PB-1969]: feat/forward a message#63
xabg2 merged 6 commits into
masterfrom
feature/forward

Conversation

@xabg2

@xabg2 xabg2 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Implementing the logic to forward a message. We format the message so the message appears after an indication of a forwarded message (----Forwarded Message---- ....).

@xabg2 xabg2 requested a review from jzunigax2 June 16, 2026 15:24
@xabg2 xabg2 self-assigned this Jun 16, 2026
@xabg2 xabg2 added the enhancement New feature or request label Jun 16, 2026
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying mail-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: c1efe02
Status: ✅  Deploy successful!
Preview URL: https://2cb2df02.mail-web-ea0.pages.dev
Branch Preview URL: https://feature-forward.mail-web-ea0.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jun 18, 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 3 minutes and 21 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 adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

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: f9688dc6-6f03-4c97-99fd-2ec1a7e8c001

📥 Commits

Reviewing files that changed from the base of the PR and between b8a9ec2 and c1efe02.

📒 Files selected for processing (8)
  • src/components/compose-message/helpers/format-email.test.ts
  • src/components/compose-message/helpers/format-email.ts
  • src/components/compose-message/hooks/useComposeMessage.ts
  • src/components/compose-message/hooks/useInitialComposeState.test.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
📝 Walkthrough

Walkthrough

Implements end-to-end email forwarding: a formatEmailToForward helper builds localized HTML forward drafts; useAttachments gains a kind-discriminated type system with inherited attachment lifecycle; useComposeMessage replaces its parameter with a setInitialValues API; useInitialComposeState handles the forward mode; useComposeSend resolves pending inherited attachments at send time; ComposeMessageDialog wires all pieces together; and usePreviewMailActions exposes onForward. i18n strings are added in four locales.

Changes

Email Forwarding Feature

Layer / File(s) Summary
ForwardDraft type and formatEmailToForward helper
src/components/compose-message/helpers/format-email.ts, src/components/compose-message/helpers/format-email.test.ts, src/i18n/locales/*.json
ForwardDraft type and formatEmailToForward function added with address-formatting helpers. Tests cover subject prefixing, recipient clearing, HTML body, cc/bcc conditional lines, HTML escaping, and htmlBody fallback. i18n forward label and forwardAttachmentFailed error strings added in en/es/fr/it.
Inherited attachment type system and useAttachments lifecycle
src/components/compose-message/hooks/useAttachments.ts, src/components/compose-message/hooks/useAttachments.test.ts
AttachmentTask replaced by UploadedAttachment | InheritedAttachment discriminated union; pending status added; addInheritedAttachments and three state-transition callbacks (markResolvingInherited, markInheritedResolved, markInheritedFailed) introduced and exposed from the hook.
useComposeMessage setInitialValues refactor and useInitialComposeState forward mode
src/components/compose-message/hooks/useComposeMessage.ts, src/components/compose-message/hooks/useComposeMessage.test.ts, src/components/compose-message/hooks/useInitialComposeState.ts, src/components/compose-message/hooks/useInitialComposeState.test.ts
useComposeMessage drops the initialDraft parameter and exposes setInitialValues. useInitialComposeState adds a forward switch case calling formatEmailToForward and extractInheritedAttachments. Tests rewritten to use setInitialValues and extended with forward-mode assertions.
useComposeSend inherited attachment resolution during send
src/components/compose-message/hooks/useComposeSend.ts, src/components/compose-message/hooks/useComposeSend.test.ts
UseComposeSendParams gains three inherited-state callbacks. handleInheritAttachments decrypts session keys, downloads blobs, re-uploads them, updates per-attachment state, and aborts with toast on failure. The send pipeline awaits this helper instead of filtering done attachments directly.
ComposeMessageDialog forward wiring
src/components/compose-message/index.tsx
Dialog moves compose initialization to useEffect-based setInitialValues calls, adds a TipTap editor body effect for forward mode, hydrates inherited attachments exactly once via hydratedForwardAttachmentsRef, and passes inherited-attachment lifecycle callbacks into useComposeSend.
usePreviewMailActions onForward action
src/hooks/mail/usePreviewMailActions.ts, src/hooks/mail/usePreviewMailActions.test.ts
onForward callback opens the compose dialog in forward mode with decryptedMail as sourceMail, replacing the prior noop. Tests verify forward and reply dialog launches and guard behavior when decryptedMail is undefined.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • jzunigax2
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being implemented: forwarding messages with a task reference.
Description check ✅ Passed The description accurately explains the forwarding implementation and the visual formatting of forwarded messages, relating directly to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/forward

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.

Base automatically changed from feature/reply-messages to master June 19, 2026 07:16
@xabg2 xabg2 marked this pull request as ready for review June 19, 2026 07:16

@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: 12

Caution

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

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

60-121: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Forward orchestration logic should be moved out of the component.

The new initialization/hydration/send wiring logic is implemented in ComposeMessageDialog instead of a hook (use<Feature>). This makes the component stateful and orchestration-heavy.

As per coding guidelines, components should be dumb (render-only), with logic extracted into hooks.

🤖 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 60 - 121, Extract the
forward orchestration logic currently in ComposeMessageDialog into a custom hook
(e.g., useComposeMessageOrchestration or useForwardMessageOrchestration). Move
the three useEffect blocks, the hydratedForwardAttachmentsRef tracking, and the
related state management logic (setInitialValues, the forward mode editor
content setup, and the inherited attachments hydration via
addInheritedAttachments) into this new hook. Have the hook return the necessary
values and callbacks, then update the component to use this hook instead of
managing these concerns directly, keeping the component focused on rendering
only.

Source: Coding guidelines

src/components/compose-message/hooks/useComposeSend.test.ts (1)

43-65: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit tests for inherited-resolution success/failure behavior.

The helper now wires inherited lifecycle callbacks, but this suite should also assert:

  1. pending inherited attachments are resolved and included in payload, and
  2. failure path shows error and aborts sendEmail.

As per coding guidelines, tests should validate behavior (including error/rejection paths), not just setup.

🤖 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.test.ts` around lines 43
- 65, The renderSend helper function sets up mocked callbacks for inherited
resolution (markResolvingInherited, markInheritedResolved, markInheritedFailed)
but there are no test cases that actually validate these callbacks are invoked
or that test the inherited attachment resolution behavior. Add new test cases
that verify: 1) when inherited attachments are pending, the success path
resolves them and includes them in the email payload while calling the
appropriate callbacks, and 2) when inherited attachment resolution fails, the
error is handled correctly and sendEmail is aborted while the failure callback
is invoked. These tests should use the renderSend helper and assert on the mock
callback invocations and behavior outcomes.

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/helpers/format-email.ts`:
- Line 39: The formatAddress call on line 39 directly accesses mail.from[0]
without verifying that the from array exists and contains at least one element.
Add a guard condition to check if mail.from has elements before attempting to
access mail.from[0]. If the from array is empty, either skip adding this field
to the formatted output or provide a fallback value such as an empty string or a
default sender label to prevent the compose initialization crash.
- Line 55: The hardcoded `Fwd:` prefix in the subject line assignment is
case-sensitive and not localized, which can cause duplicate prefixes like `fwd:`
and prevents proper translation. Extract the hardcoded `Fwd:` string into a
localized translation key that will be defined across all required locales (en,
es, fr, it), then update the startsWith check to perform a case-insensitive
comparison by converting both the mail.subject and the localized prefix to the
same case before checking, ensuring the final subject uses the properly
localized prefix from the translation system.
- Around line 25-42: The formatAddress function interpolates the untrusted
address.name field directly into the HTML string without escaping, and the
formatEmailToForward function similarly interpolates mail.subject without
escaping. Both of these untrusted values should be HTML-escaped before being
included in the formatted strings to prevent HTML injection attacks. Use an HTML
escaping utility function (such as one from a library like html-escape or
lodash) to escape the address.name value in the formatAddress function return
statement and the mail.subject value in the lines array within
formatEmailToForward function before interpolating them into their respective
strings.

In `@src/components/compose-message/hooks/useAttachments.test.ts`:
- Around line 72-74: The test suite for useAttachments hook is missing coverage
for the newly introduced inherited attachment lifecycle paths. Add test cases
that exercise the addInheritedAttachments function and its state transitions
including resolving, resolved, failed states, and the inherited size-limit
validation path. Ensure tests cover the happy path (successful inheritance),
edge cases (boundary conditions for size limits), error paths (failed
inheritance scenarios), and rejection paths (error handling during resolution)
to meet the coding guidelines requirement of comprehensive test coverage for all
logic paths.

In `@src/components/compose-message/hooks/useAttachments.ts`:
- Around line 93-133: Failed inherited attachments get stuck in error state
because markInheritedFailed sets status to 'error' but the send pipeline only
processes items with status 'pending', leaving no way to retry. Add a new
useCallback hook (similar to markResolvingInherited and markInheritedFailed)
that resets a failed inherited attachment's status back to 'pending' so it can
be reprocessed by the send pipeline, allowing users to retry failed inherited
attachments.

In `@src/components/compose-message/hooks/useComposeMessage.test.ts`:
- Line 23: The describe block titles in useComposeMessage.test.ts are not
following the required behavior-style phrasing pattern. Rename the describe
blocks at line 23 and line 196 to use the plain-language "When ..., then ..."
format instead of the current imperative style. For example, "Hydrating from an
existing draft" should be changed to "When hydrating from an existing draft,
then ..." to align with the coding guidelines for test descriptions.

In `@src/components/compose-message/hooks/useComposeMessage.ts`:
- Around line 55-62: Wrap the setInitialValues function with useCallback to
ensure consistency with other callback handlers in this hook and prevent
unnecessary reruns in downstream consumers. Import useCallback from React if not
already imported, then wrap the setInitialValues function definition with
useCallback and provide an empty dependency array since the function only calls
stable state setters (setSubjectValue, setToRecipients, setCcRecipients,
setBccRecipients, setShowCc, setShowBcc).

In `@src/components/compose-message/hooks/useComposeSend.ts`:
- Line 125: Remove the console.error call that logs 'Failed to re-encrypt
inherited attachment' in the useComposeSend hook and replace it with a
centralized error logging utility that adheres to the project's logging
standards. Identify the appropriate error handling or logging service used
elsewhere in the codebase for similar error scenarios and route this error
through that centralized mechanism instead of direct console output.
- Around line 173-174: The send() function calls handleInheritAttachments() to
resolve inherited attachments but does not check whether it succeeded or failed
before proceeding to call sendEmail. If handleInheritAttachments() returns early
due to a failure, the send operation should also be aborted to prevent silently
sending a message without the intended forwarded attachments. Add a validation
check after the await attachmentsToSend assignment that verifies the result is
valid, and if not, return early from the send() function to prevent sendEmail
from being called.

In `@src/components/compose-message/hooks/useInitialComposeState.test.ts`:
- Line 7: The test file contains duplicate mock declarations for `@/i18n` and
duplicate test blocks. Remove the duplicate vi.mock statement for `@/i18n`
(keeping only one instance of the mock that provides the useTranslationContext
hook returning the translate function). Additionally, remove the duplicate set
of three forward-mode tests that appear later in the file (around lines
111-145), keeping only the original instances to eliminate noise and maintenance
overhead.

In `@src/components/compose-message/hooks/useInitialComposeState.ts`:
- Around line 3-6: The relative imports for internal modules should be replaced
with the `@/` alias to follow the repository's coding standards. In the
useInitialComposeState.ts file, replace the relative imports for
formatEmailToForward and formatEmailToReply from '../helpers/format-email' with
`@/components/compose-message/helpers/format-email`, and replace the relative
import for the Recipient type from '../types' with
`@/components/compose-message/types`. The relative import for useAttachments in
the same directory can remain unchanged.
- Around line 62-79: The replyAll case is incorrectly being routed through the
same code path as forward by falling through to the formatEmailToForward
function, which clears recipients and applies forward semantics. Split the
replyAll and forward cases into separate handling blocks: for replyAll, extract
recipients (to, cc, bcc) directly from compose.sourceMail and preserve them in
the returned data object, while keeping the forward case handling as it
currently is with formatEmailToForward and the subject prefix logic.

---

Outside diff comments:
In `@src/components/compose-message/hooks/useComposeSend.test.ts`:
- Around line 43-65: The renderSend helper function sets up mocked callbacks for
inherited resolution (markResolvingInherited, markInheritedResolved,
markInheritedFailed) but there are no test cases that actually validate these
callbacks are invoked or that test the inherited attachment resolution behavior.
Add new test cases that verify: 1) when inherited attachments are pending, the
success path resolves them and includes them in the email payload while calling
the appropriate callbacks, and 2) when inherited attachment resolution fails,
the error is handled correctly and sendEmail is aborted while the failure
callback is invoked. These tests should use the renderSend helper and assert on
the mock callback invocations and behavior outcomes.

In `@src/components/compose-message/index.tsx`:
- Around line 60-121: Extract the forward orchestration logic currently in
ComposeMessageDialog into a custom hook (e.g., useComposeMessageOrchestration or
useForwardMessageOrchestration). Move the three useEffect blocks, the
hydratedForwardAttachmentsRef tracking, and the related state management logic
(setInitialValues, the forward mode editor content setup, and the inherited
attachments hydration via addInheritedAttachments) into this new hook. Have the
hook return the necessary values and callbacks, then update the component to use
this hook instead of managing these concerns directly, keeping the component
focused on rendering only.
🪄 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: b9815206-306b-43ea-b6d2-63a0bec4654c

📥 Commits

Reviewing files that changed from the base of the PR and between ce8f2be and b8a9ec2.

📒 Files selected for processing (17)
  • src/components/compose-message/helpers/format-email.test.ts
  • src/components/compose-message/helpers/format-email.ts
  • src/components/compose-message/hooks/useAttachments.test.ts
  • src/components/compose-message/hooks/useAttachments.ts
  • src/components/compose-message/hooks/useComposeMessage.test.ts
  • src/components/compose-message/hooks/useComposeMessage.ts
  • src/components/compose-message/hooks/useComposeSend.test.ts
  • src/components/compose-message/hooks/useComposeSend.ts
  • src/components/compose-message/hooks/useInitialComposeState.test.ts
  • src/components/compose-message/hooks/useInitialComposeState.ts
  • src/components/compose-message/index.tsx
  • src/hooks/mail/usePreviewMailActions.test.ts
  • src/hooks/mail/usePreviewMailActions.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json

Comment thread src/components/compose-message/helpers/format-email.ts
Comment thread src/components/compose-message/helpers/format-email.ts Outdated
Comment thread src/components/compose-message/helpers/format-email.ts Outdated
Comment thread src/components/compose-message/hooks/useAttachments.test.ts
Comment thread src/components/compose-message/hooks/useAttachments.ts
Comment thread src/components/compose-message/hooks/useComposeSend.ts
Comment on lines +173 to 174
const attachmentsToSend = await handleInheritAttachments();

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 | ⚡ Quick win

Abort sending when inherited attachment resolution fails.

handleInheritAttachments() can return early on failure, but send() continues and still calls sendEmail. That can silently send a message without intended forwarded attachments.

Suggested fix
 const attachmentsToSend = await handleInheritAttachments();
+if (!attachmentsToSend) return;
📝 Committable suggestion

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

Suggested change
const attachmentsToSend = await handleInheritAttachments();
const attachmentsToSend = await handleInheritAttachments();
if (!attachmentsToSend) return;
🤖 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 173 -
174, The send() function calls handleInheritAttachments() to resolve inherited
attachments but does not check whether it succeeded or failed before proceeding
to call sendEmail. If handleInheritAttachments() returns early due to a failure,
the send operation should also be aborted to prevent silently sending a message
without the intended forwarded attachments. Add a validation check after the
await attachmentsToSend assignment that verifies the result is valid, and if
not, return early from the send() function to prevent sendEmail from being
called.

Comment thread src/components/compose-message/hooks/useInitialComposeState.test.ts Outdated
Comment thread src/components/compose-message/hooks/useInitialComposeState.ts
Comment thread src/components/compose-message/hooks/useInitialComposeState.ts
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@xabg2 xabg2 merged commit 710be09 into master Jun 19, 2026
5 of 6 checks passed
@xabg2 xabg2 deleted the feature/forward branch June 19, 2026 13:53
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