Skip to content

[PB-6144]: feat/complete mail view#20

Merged
xabg2 merged 4 commits intomasterfrom
feature/preview-mail
Mar 20, 2026
Merged

[PB-6144]: feat/complete mail view#20
xabg2 merged 4 commits intomasterfrom
feature/preview-mail

Conversation

@xabg2
Copy link
Contributor

@xabg2 xabg2 commented Mar 20, 2026

Adding the mail preview component so the mail view is completed (Tray list + preview mail component).

We will need to handle the attachments, but it will be done in another task.

@xabg2 xabg2 requested a review from larryrider March 20, 2026 11:33
@xabg2 xabg2 self-assigned this Mar 20, 2026
@xabg2 xabg2 added the enhancement New feature or request label Mar 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Refactors mail UI into separate tray and preview components, adds Instrument Sans font and global CSS updates, introduces DateService with tests, adds HTML sanitization for mail previews, updates dependencies (including dompurify), expands test fixtures, and adjusts SDK/Auth logout and unauthorized handling.

Changes

Cohort / File(s) Summary
HTML & Global Styles
index.html, src/index.css, src/assets/fonts/InstrumentSans/*
Switched referenced stylesheet in index.html to src/index.css; imported Instrument Sans via new font CSS; added font @font-face and OFL license file; set global font-family to Instrument Sans.
Dependencies
package.json
Bumped dayjs, updated @internxt/ui range, added dompurify and @types/dompurify.
Mail view & tray
src/features/mail/MailView.tsx, src/features/mail/components/tray/index.tsx
Refactored MailView to use new TrayList for the tray and PreviewMail for message preview; introduced TrayList component rendering a Tray with empty-state.
Mail preview components
src/features/mail/components/mail-preview/*
Added PreviewMail, PreviewHeader (recipient lines, attachments), and Preview (sanitizes HTML with DOMPurify, enforces link attrs, renders attachments).
User UI
src/components/user-chip/index.tsx
Added UserChip component with hover tooltip rendered via portal and ARIA relationships.
Date service & tests
src/services/date/index.ts, src/services/date/date.service.test.ts
Added DateService (formatting, relative time, locale setter) and comprehensive Vitest suite using fake timers.
i18n updates
src/i18n/services/i18n.service.ts, src/i18n/locales/en.json
Registered additional dayjs locales (es, fr, it), switched localStorage access to LocalStorageService.instance, and added mail recipient i18n strings.
SDK / Auth changes & tests
src/services/sdk/auth/index.ts, src/services/sdk/index.ts, src/services/sdk/sdk.service.test.ts, src/services/sdk/auth/auth.service.test.ts
Made AuthService.logOut await logout; SdkManager reads LocalStorage singleton directly and enhanced unauthorizedCallback to call AuthService.instance.logOut then navigate; updated tests to assert logOut is invoked and added auth service test.
Test fixtures
src/test-utils/fixtures.ts
Added email fixture helpers: getMockedMail(), getMockedMails(count), and getMockedMailBoxes().
Misc
src/routes/guards/ProtectedRoute.tsx
Added a TODO comment about updating mail destination to Inbox (no functional change).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • larryrider
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'PB-6144: feat/complete mail view' directly describes the main objective of the changeset: completing the mail view by adding the preview component alongside the tray list.
Description check ✅ Passed The description is directly related to the changeset, explaining that the PR adds the mail preview component to complete the mail view with tray list and preview components, noting attachments will be handled separately.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/preview-mail

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.

Copy link

@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.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 40: Add the missing TypeScript types by adding `@types/dompurify` to
devDependencies so the compiler recognizes DOMPurify types; update package.json
devDependencies to include "@types/dompurify" (matching the installed
"dompurify" dependency) and run the install step, ensuring imports/usages like
DOMPurify.sanitize in preview/index.tsx resolve without type errors.

In `@src/assets/fonts/InstrumentSans/font.css`:
- Around line 3-10: The `@font-face` for 'Instrument Sans' is missing a
font-weight range causing browsers to extrapolate; update the `@font-face` rule
(for the font-family 'Instrument Sans' referencing
InstrumentSans-variable.woff2) to include a font-weight descriptor specifying
the variable font's supported min and max weight (e.g. the actual range such as
100 900) so weight interpolation and font matching work correctly.

In `@src/components/UserChip/index.tsx`:
- Around line 21-27: Move the role="tooltip" off the trigger container and onto
the actual tooltip element that is rendered/portaled (the element created in the
portal/tooltip renderer inside UserChip); update the trigger container (the div
with ref and onMouseEnter/onMouseLeave and functions handleMouseEnter and
setPosition) to include aria-describedby pointing to the tooltip's id, and
ensure the tooltip element has an id and role="tooltip" so screen readers
associate the described content correctly.
- Line 1: Update the misspelled import and component usage from UserCheap to
UserChip: change the import declaration that currently imports UserCheap from
'@internxt/ui' to import UserChip, and update the JSX usage where <UserCheap ...
/> is rendered to <UserChip ... /> so the symbol matches the exported component
name and resolves the TS2305 error.

In `@src/features/mail/components/mailPreview/header/index.tsx`:
- Around line 28-30: The current users.map rendering uses user.email as the
React key for <UserChip> which can collide when the same email appears multiple
times in the same recipients array; update the key generation in the users.map
call (where UserChip is returned) to produce a stable, unique key per item (for
example combine user.email with the map index or a recipient role/id: e.g.,
`${user.email}-${index}` or a dedicated unique id property) so that UserChip has
a non-duplicating key even when identical emails appear multiple times.

In `@src/features/mail/components/mailPreview/preview/index.tsx`:
- Around line 19-25: The external links rendered in the attachments map
(attachments?.map(...) in the preview component) use rel="noreferrer" only;
update the anchor elements to include rel="noopener noreferrer" so you
explicitly enable noopener behavior for legacy browsers and clarity—locate the
JSX inside the attachments mapping where the <a href={attachment}
target="_blank" rel="noreferrer"> is returned and change the rel value
accordingly.

In `@src/features/mail/components/tray/index.tsx`:
- Around line 9-20: TrayList currently forces a loading state and an empty mails
array; update the TrayListProps type to include optional loading?: boolean and
mails?: Mail[] (use the correct Mail item type used by Tray), change the
TrayList signature to accept these props (e.g., ({ folderName, loading, mails }:
TrayListProps) ), and forward them into the Tray component (Tray
loading={loading ?? true} mails={mails ?? []} emptyState={<TrayEmptyState
folderName={folderName} />}) so callers can control loading and mail data while
preserving current defaults; reference TrayListProps, TrayList, Tray and
TrayEmptyState when making the changes.

In `@src/features/mail/MailView.tsx`:
- Line 3: The MailView component imports and calls getMockedMail (from
test-utils/fixtures) on every render which produces random data and causes UI
instability; remove the production dependency on getMockedMail and replace it
with stable data — either fetch real mail data or create a deterministic
dev/mock object used only in MailView, and stabilize it via useMemo or useState
inside the MailView component (e.g., initialize a single mock once and reuse it)
so the mail content does not change on re-renders; also delete the import of
getMockedMail from the top of the file and ensure any references to
getMockedMail in MailView are replaced with the stable data source.

In `@src/routes/guards/ProtectedRoute.tsx`:
- Line 9: The TODO in the ProtectedRoute component is vague and non-standard;
replace the comment "// !TODO: Check if the user already updated the mail to
send him to the Inbox instead" with a clear actionable item: remove the "!"
prefix, use inclusive language, and either implement or create a tracked issue
(e.g., TODO(PB-XXXX): redirect authenticated users to Inbox if they have
configured mail) describing the exact condition (e.g., "if user.mailConfigured
=== true then navigate to /inbox") so future work is traceable; update the
comment next to the ProtectedRoute component (and any related functions like the
route redirection logic) to state whether the logic will be implemented in this
PR or tracked separately with the issue ID.

In `@src/services/date/date.service.test.ts`:
- Around line 32-44: Tests using DateService.fromNow rely on Date.now() and are
flaky; fix by using Vitest fake timers to freeze time during each test: in the
describe block or each test call vi.useFakeTimers() and
vi.setSystemTime(fixedTimestamp) before invoking DateService.fromNow(...) and
then vi.useRealTimers() (or vi.restoreAllMocks()) after to restore; ensure the
fixedTimestamp is chosen so the expected relative strings ('2 hours ago', 'in 3
hours') are deterministic.

In `@src/services/sdk/auth/index.ts`:
- Around line 7-12: The logout flow in logOut currently calls
authClient.logout(token) and only clears
LocalStorageService.instance.clearCredentials() if that call succeeds, so tokens
remain if logout throws; wrap the logout call in a try/finally inside public
logOut to ensure LocalStorageService.instance.clearCredentials() runs regardless
of authClient.logout(token) success or error (optionally log the error in the
catch or let it propagate after clearing), using SdkManager.instance.getAuth()
and LocalStorageService.instance APIs referenced in the method.

In `@src/services/sdk/index.ts`:
- Around line 34-38: The unauthorizedCallback currently calls
AuthService.instance.logOut() with a void so any rejection is swallowed; update
unauthorizedCallback to handle promise rejections from
AuthService.instance.logOut() (when LocalStorageService.instance is truthy) by
appending a .catch handler that logs the error (use existing logger or
console.error) and ensures local credentials are cleared on failure (call
LocalStorageService.instance.clearCredentials() or the equivalent cleanup
method) so failures don’t leave stale auth state.

In `@src/test-utils/fixtures.ts`:
- Around line 213-216: Fix the inconsistent spam mailbox fixture where
totalEmails and unreadEmails are generated independently: ensure unreadEmails is
constrained to be <= totalEmails by first generating a single value (e.g.,
spamTotal) for totalEmails and then generating unreadEmails with a max of that
value (reference the fields totalEmails and unreadEmails in the spam mailbox
object in src/test-utils/fixtures.ts); update the fixture so unreadEmails uses
the already-generated total value instead of an independent random range.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f863a0d7-0449-44c4-941b-bf041c2d638f

📥 Commits

Reviewing files that changed from the base of the PR and between f970042 and 3585ad1.

⛔ Files ignored due to path filters (6)
  • package-lock.json is excluded by !**/package-lock.json
  • src/assets/fonts/InstrumentSans/InstrumentSans-Bold.woff2 is excluded by !**/*.woff2
  • src/assets/fonts/InstrumentSans/InstrumentSans-Medium.woff2 is excluded by !**/*.woff2
  • src/assets/fonts/InstrumentSans/InstrumentSans-Regular.woff2 is excluded by !**/*.woff2
  • src/assets/fonts/InstrumentSans/InstrumentSans-SemiBold.woff2 is excluded by !**/*.woff2
  • src/assets/fonts/InstrumentSans/InstrumentSans-variable.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (22)
  • index.html
  • package.json
  • src/assets/fonts/InstrumentSans/OFL.txt
  • src/assets/fonts/InstrumentSans/font.css
  • src/components/UserChip/index.tsx
  • src/features/mail/MailView.tsx
  • src/features/mail/components/mailPreview/header/index.tsx
  • src/features/mail/components/mailPreview/index.tsx
  • src/features/mail/components/mailPreview/preview/index.tsx
  • src/features/mail/components/tray/header/index.tsx
  • src/features/mail/components/tray/index.tsx
  • src/features/mail/components/tray/searchInput/index.tsx
  • src/features/mail/components/tray/trayEmptyState/index.tsx
  • src/i18n/services/i18n.service.ts
  • src/index.css
  • src/routes/guards/ProtectedRoute.tsx
  • src/services/date/date.service.test.ts
  • src/services/date/index.ts
  • src/services/sdk/auth/index.ts
  • src/services/sdk/index.ts
  • src/services/sdk/sdk.service.test.ts
  • src/test-utils/fixtures.ts

Copy link

@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.

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

♻️ Duplicate comments (1)
src/components/UserChip/index.tsx (1)

21-29: ⚠️ Potential issue | 🟡 Minor

Accessibility: Remove role="tooltip" from the trigger element.

The aria-describedby and tooltipId additions are correct, but the trigger container (line 27) still has role="tooltip" which should only be on the actual tooltip element (line 36). Having role="tooltip" on both elements is semantically incorrect.

♿ Proposed fix
     <div
       ref={ref}
       className="relative inline-block"
       onMouseEnter={handleMouseEnter}
       onMouseLeave={() => setPosition(null)}
-      role="tooltip"
       aria-describedby={position ? tooltipId : undefined}
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/UserChip/index.tsx` around lines 21 - 29, Remove the erroneous
role="tooltip" from the trigger div in the UserChip component: locate the JSX
block that returns the div using ref, handleMouseEnter, setPosition and
aria-describedby={position ? tooltipId : undefined} and delete the role prop so
only the actual tooltip element (the element using tooltipId, rendered when
position is set) retains role="tooltip"; leave aria-describedby in place and
ensure the tooltip element still has role="tooltip" and id={tooltipId}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/mail/components/mailPreview/header/index.tsx`:
- Around line 55-60: The conditional rendering currently uses "attachmentsLength
&& attachmentsLength > 0" which can evaluate to 0 and render that literal;
update the JSX condition to explicitly check greater-than-zero (i.e., replace
the condition with "attachmentsLength > 0") where attachmentsLength is used in
the mail preview header render so the PaperclipIcon and count only render when
attachmentsLength is positive.

In `@src/services/date/date.service.test.ts`:
- Around line 17-20: Tests assume English locale causing brittle expectations;
in the test suite for DateService (referencing DateService.format and relative
formatting assertions) add a beforeEach that explicitly sets the locale to
English (e.g., call DateService.setLocale('en') or dayjs.locale('en')) and
optionally restore/reset afterEach so the global locale cannot leak between
tests; target the describe block containing the tests that use FIXED_DATE and
relative strings like "2 hours ago" so assertions remain deterministic.

In `@src/services/sdk/auth/auth.service.test.ts`:
- Around line 8-20: The test title claims "the token should be cleared" but only
asserts logout was called; update the test to also assert that
LocalStorageService.instance.clearCredentials() is invoked after
AuthService.instance.logOut(), by adding a spy
(vi.spyOn(LocalStorageService.instance, 'clearCredentials')) before calling
logOut and an expect toHaveBeenCalled() after; alternatively, if you prefer not
to assert clearCredentials here, change the test title to reflect that it only
verifies mockedAuthClient.logout(mockedToken) is called.
- Around line 7-20: The test sets spies on LocalStorageService.instance.getToken
and SdkManager.instance.getAuth but does not restore them, which can leak into
other tests; add cleanup to restore spies after each test (e.g., an afterEach
that calls vi.restoreAllMocks() or explicitly calls mockRestore on the spies) so
that LocalStorageService.instance.getToken and SdkManager.instance.getAuth are
reset; ensure this cleanup runs for the AuthService.instance.logOut test and any
others in the describe block.

In `@src/services/sdk/index.ts`:
- Line 42: The code calls NavigationService.instance.navigate({ id:
AppView.Welcome }) before the async logOut() finishes, allowing navigation while
logout is in progress; either await the logout call (e.g., await
authService.logOut() or ensure the function containing logOut returns a Promise
and await it) before calling NavigationService.instance.navigate, or if
immediate redirect is intentional add an inline comment above
NavigationService.instance.navigate explaining that navigation should be
immediate and logout can continue in background; update the function containing
logOut and NavigationService.instance.navigate to implement the chosen approach
and keep method names logOut and NavigationService.instance.navigate as the
reference points.
- Around line 36-43: The unauthorizedCallback currently calls
LocalStorageService.instance.clearCredentials() before
AuthService.instance.logOut(), which causes AuthService.logOut() (which calls
LocalStorageService.instance.getToken() and ultimately authClient.logout(token))
to receive null; reorder the calls so AuthService.instance.logOut() is invoked
first (so it can read the existing token via
LocalStorageService.instance.getToken() and call authClient.logout(token)), then
call LocalStorageService.instance.clearCredentials() and finally
NavigationService.instance.navigate({ id: AppView.Welcome }).

In `@src/services/sdk/sdk.service.test.ts`:
- Around line 188-196: The test "When Users client receives unauthorized
response, then logOut should be called" currently triggers the
unauthorizedCallback but only asserts AuthService.instance.logOut; update the
test to also assert that NavigationService.instance.navigate was called with the
welcome view (verify NavigationService.instance.navigate was invoked with an
object containing id: AppView.Welcome) after calling
securityArg.unauthorizedCallback(), ensuring NavigationService is properly
spied/mocked in the test setup before invocation.

---

Duplicate comments:
In `@src/components/UserChip/index.tsx`:
- Around line 21-29: Remove the erroneous role="tooltip" from the trigger div in
the UserChip component: locate the JSX block that returns the div using ref,
handleMouseEnter, setPosition and aria-describedby={position ? tooltipId :
undefined} and delete the role prop so only the actual tooltip element (the
element using tooltipId, rendered when position is set) retains role="tooltip";
leave aria-describedby in place and ensure the tooltip element still has
role="tooltip" and id={tooltipId}.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f91a8536-0ed7-45d9-b79b-ac5fbcb5a866

📥 Commits

Reviewing files that changed from the base of the PR and between 3585ad1 and 43a3413.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json
  • src/components/UserChip/index.tsx
  • src/features/mail/components/mailPreview/header/index.tsx
  • src/services/date/date.service.test.ts
  • src/services/sdk/auth/auth.service.test.ts
  • src/services/sdk/auth/index.ts
  • src/services/sdk/index.ts
  • src/services/sdk/sdk.service.test.ts

Copy link

@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.

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

♻️ Duplicate comments (2)
src/features/mail/MailView.tsx (1)

3-3: ⚠️ Potential issue | 🔴 Critical

Wire MailView to real mailbox state before merging.

This composition is still placeholder-driven: getMockedMail() regenerates a random message on every render, and TrayList currently hardcodes loading={true} / mails={[]} in src/features/mail/components/tray/index.tsx:92-109. As merged, the screen can never show the actual folder contents or selected message, so please replace the fixture/placeholder wiring with real state or at least freeze any temporary mock behind stable state while the data layer lands. Please verify the full folder → selection → preview flow before merging.

Also applies to: 14-18, 25-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/mail/MailView.tsx` at line 3, MailView currently uses
getMockedMail() which regenerates on each render and TrayList is hardcoded with
loading={true} and mails={[]} so the UI can't show real folder/selection state;
update MailView to stop calling getMockedMail() directly on render and instead
wire it to the real mailbox state (or a stable local state) by reading from the
mailbox store/prop (e.g., useMailbox, mailboxState, or pass down mails/folders)
and ensure TrayList receives dynamic props (loading flag and mails array) from
that state rather than hardcoded values; if the backend isn't ready, initialize
a stable mocked state once (use useState/useMemo to freeze a fixture) and
connect selection handlers so the folder → selection → preview flow (TrayList
selection -> selectedMail in MailView -> MailPreview) works end-to-end.
src/components/user-chip/index.tsx (1)

22-39: ⚠️ Potential issue | 🟠 Major

Use a focusable inline trigger for the tooltip.

The root element is a mouse-only <div> with role="tooltip", so keyboard users can't reveal the recipient details, and embedding this component inside RecipientLine's <span> wrappers produces invalid DOM nesting. Render the trigger as an inline focusable element (button/span), move role="tooltip" to the portaled popup only, and mirror the open/close logic on focus/blur. Please verify the recipient chips with keyboard-only navigation before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/user-chip/index.tsx` around lines 22 - 39, Replace the root
non-focusable div trigger with a focusable inline element (e.g., a <button
type="button"> or a <span tabIndex={0}>) so keyboard users can open the tooltip;
keep the same className and ref usage (ref) but remove role="tooltip" from the
trigger and ensure the portaled popup created by createPortal retains
role="tooltip" and id={tooltipId}; mirror mouse handlers by adding
onFocus={handleMouseEnter} and onBlur={() => setPosition(null)} alongside
onMouseEnter/onMouseLeave so open/close logic (handleMouseEnter, setPosition,
tooltipId) works for keyboard and pointer, and keep aria-describedby on the
trigger conditional on position (aria-describedby={position ? tooltipId :
undefined}) to maintain accessibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/mail/components/mail-preview/header/index.tsx`:
- Around line 46-48: Recipient labels are hardcoded; update header to use
localized strings instead of literals by pulling translations via the i18n hook
or by accepting translated props from MailView. Replace the hardcoded "To:",
"CC:", "BCC:" passed to RecipientLine with localized values (e.g., const t =
useTranslation()/useI18n(); then t('mail.to'), t('mail.cc'), t('mail.bcc')) or
change MailView to pass translated strings into this header component (e.g.,
props.toLabel, props.ccLabel, props.bccLabel) and use those in RecipientLine.
Ensure the translation keys are named consistently with the repo's i18n keys and
update call sites (MailView) if you choose the prop approach.

In `@src/features/mail/components/mail-preview/preview/index.tsx`:
- Around line 10-16: The HTML is being sanitized with DOMPurify.sanitize(body)
but remote images/tracking pixels remain allowed and will load when injected via
dangerouslySetInnerHTML; update the sanitization to disallow remote URIs (or
strip image/link tags) before rendering: either call DOMPurify.sanitize(body, {
ALLOWED_URI_REGEXP: /^(?:data:|\/|#)/i }) to only allow data and relative/local
URIs, or pre-process the incoming body (e.g., remove/neutralize <img>, <iframe>,
and <link rel> tags with http(s) src/href) and then assign the result to
sanitizedBody used by the component that calls dangerouslySetInnerHTML; ensure
the change targets the sanitizedBody construction (DOMPurify.sanitize) and the
component rendering that value.

---

Duplicate comments:
In `@src/components/user-chip/index.tsx`:
- Around line 22-39: Replace the root non-focusable div trigger with a focusable
inline element (e.g., a <button type="button"> or a <span tabIndex={0}>) so
keyboard users can open the tooltip; keep the same className and ref usage (ref)
but remove role="tooltip" from the trigger and ensure the portaled popup created
by createPortal retains role="tooltip" and id={tooltipId}; mirror mouse handlers
by adding onFocus={handleMouseEnter} and onBlur={() => setPosition(null)}
alongside onMouseEnter/onMouseLeave so open/close logic (handleMouseEnter,
setPosition, tooltipId) works for keyboard and pointer, and keep
aria-describedby on the trigger conditional on position
(aria-describedby={position ? tooltipId : undefined}) to maintain accessibility.

In `@src/features/mail/MailView.tsx`:
- Line 3: MailView currently uses getMockedMail() which regenerates on each
render and TrayList is hardcoded with loading={true} and mails={[]} so the UI
can't show real folder/selection state; update MailView to stop calling
getMockedMail() directly on render and instead wire it to the real mailbox state
(or a stable local state) by reading from the mailbox store/prop (e.g.,
useMailbox, mailboxState, or pass down mails/folders) and ensure TrayList
receives dynamic props (loading flag and mails array) from that state rather
than hardcoded values; if the backend isn't ready, initialize a stable mocked
state once (use useState/useMemo to freeze a fixture) and connect selection
handlers so the folder → selection → preview flow (TrayList selection ->
selectedMail in MailView -> MailPreview) works end-to-end.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da29e532-c292-4084-a938-04d33a0be40a

📥 Commits

Reviewing files that changed from the base of the PR and between 43a3413 and f44636f.

📒 Files selected for processing (5)
  • src/components/user-chip/index.tsx
  • src/features/mail/MailView.tsx
  • src/features/mail/components/mail-preview/header/index.tsx
  • src/features/mail/components/mail-preview/index.tsx
  • src/features/mail/components/mail-preview/preview/index.tsx

@sonarqubecloud
Copy link

@xabg2 xabg2 merged commit 4ae76a5 into master Mar 20, 2026
4 of 5 checks passed
@xabg2 xabg2 deleted the feature/preview-mail branch March 20, 2026 16:24
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