fix: hold the offline notice until the welcome tips are closed, also when they show in the reading pane

This commit is contained in:
Linus Rath
2026-11-04 12:41:37 +01:00
parent badbd616df
commit 68bdaaac33
3 changed files with 24 additions and 6 deletions
+2 -3
View File
@@ -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 && <MessageListTabs panelId={messageListPanelId} />}
{!emptyPaneBesideList && <WelcomeBanner />}
<FirstStartNoticeBar hidden={welcomeDue && !emptyPaneBesideList} />
<FirstStartNoticeBar />
<div {...messageListPanelProps} className="flex-1 min-h-0 flex flex-col overflow-hidden">
<ErrorBoundary fallback={EmailListErrorFallback}>
@@ -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(<FirstStartNoticeBar />);
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(<FirstStartNoticeBar />);
act(() => {
+7 -3
View File
@@ -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 (
<div
role="status"