From 68bdaaac33620cc879657bcd68ba7fab212f5b15 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Wed, 4 Nov 2026 12:41:37 +0100
Subject: [PATCH] fix: hold the offline notice until the welcome tips are
closed, also when they show in the reading pane
---
components/mail/mail-app.tsx | 5 ++---
.../ui/__tests__/first-start-notice.test.tsx | 15 +++++++++++++++
components/ui/first-start-notice.tsx | 10 +++++++---
3 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx
index bdc888cfc..e89c23cec 100644
--- a/components/mail/mail-app.tsx
+++ b/components/mail/mail-app.tsx
@@ -109,7 +109,7 @@ import type { Attachment, SendEmailResult } from "@/lib/jmap/types";
import type { IJMAPClient } from "@/lib/jmap/client-interface";
import { peekListAttachments, requestListAttachments, type ListAttachmentSource, type LoadListAttachments } from "@/lib/list-attachments";
import { useSearchHistoryStore } from "@/stores/search-history-store";
-import { useWelcomeBannerDue, WelcomeBanner } from "@/components/ui/welcome-banner";
+import { WelcomeBanner } from "@/components/ui/welcome-banner";
import { FirstStartNoticeBar } from "@/components/ui/first-start-notice";
import { NavigationRail } from "@/components/layout/navigation-rail";
import { AppMain } from "@/components/layout/app-landmarks";
@@ -262,7 +262,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
const linkSegments = useLiteLinkSegments('mail', routeSegments);
const t = useTranslations();
// First-start notices wait until the welcome tips are closed: one thing at a time.
- const welcomeDue = useWelcomeBannerDue();
// The page skeleton the gates below draw: the same one the server sent.
const skeletonLabels = useSkeletonLabels();
const tNotifications = useTranslations('notifications');
@@ -5781,7 +5780,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
unless an enabled plugin registered tabs via api.tabs.set. */}
{!isScheduledView && }
{!emptyPaneBesideList && }
-
+
diff --git a/components/ui/__tests__/first-start-notice.test.tsx b/components/ui/__tests__/first-start-notice.test.tsx
index 42b4975df..3f891cf14 100644
--- a/components/ui/__tests__/first-start-notice.test.tsx
+++ b/components/ui/__tests__/first-start-notice.test.tsx
@@ -2,15 +2,30 @@ import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { FirstStartNoticeBar } from '../first-start-notice';
import { resetFirstStartNotices, showFirstStartNotice } from '@/lib/first-start-notices';
+import { useSettingsStore } from '@/stores/settings-store';
beforeEach(() => {
localStorage.clear();
resetFirstStartNotices();
+ // The welcome tips were closed: notices may show.
+ useSettingsStore.setState({ onboardingCompleted: true, showOnboardingOnNewDevices: false });
});
afterEach(() => cleanup());
describe('first-start notices', () => {
+ it('wait for the welcome tips, wherever they show, and come after Got it', () => {
+ useSettingsStore.setState({ onboardingCompleted: false });
+ render();
+ act(() => showFirstStartNotice({ id: 'offline', text: 'Mail is kept on this device.' }));
+ expect(screen.queryByText('Mail is kept on this device.')).toBeNull();
+ act(() => {
+ localStorage.setItem('onboarding_completed', 'true');
+ useSettingsStore.setState({ onboardingCompleted: true });
+ });
+ expect(screen.getByText('Mail is kept on this device.')).toBeTruthy();
+ });
+
it('show one at a time, each with a close button, the next once the first is closed', () => {
render();
act(() => {
diff --git a/components/ui/first-start-notice.tsx b/components/ui/first-start-notice.tsx
index ea0dbc890..a9b9d0fbd 100644
--- a/components/ui/first-start-notice.tsx
+++ b/components/ui/first-start-notice.tsx
@@ -5,16 +5,20 @@ import { Info, X } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { IconButton } from "@/components/ui/icon-button";
import { closeFirstStartNotice, useCurrentFirstStartNotice } from "@/lib/first-start-notices";
+import { useWelcomeBannerDue } from "@/components/ui/welcome-banner";
/**
* The first-start notice that's waiting, as a bar above the message list (see
- * lib/first-start-notices). In the page's flow, it covers nothing. `hidden` while
- * something else greets the user there, so only one shows at a time.
+ * lib/first-start-notices). In the page's flow, it covers nothing. It waits while the
+ * welcome tips are due, wherever they show (above the list or in the reading pane),
+ * so the first start has one greeting at a time. `hidden` while something else greets
+ * the user there.
*/
export function FirstStartNoticeBar({ hidden = false }: { hidden?: boolean }) {
const tCommon = useTranslations("common");
const notice = useCurrentFirstStartNotice();
- if (!notice || hidden) return null;
+ const welcomeDue = useWelcomeBannerDue();
+ if (!notice || hidden || welcomeDue) return null;
return (