From d83c83b0a9f7bdcd1e2a8cfc1e63d0cbc3242d40 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:11:25 +0200 Subject: [PATCH] fix: drop the folder sidebar from fullscreen email --- components/mail/mail-app.tsx | 21 ------- components/pro/pro-email-tab-body.tsx | 84 ++++----------------------- lib/pro-mail-navigation.ts | 40 ------------- 3 files changed, 10 insertions(+), 135 deletions(-) delete mode 100644 lib/pro-mail-navigation.ts diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 42a37c134..250f19e15 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -106,7 +106,6 @@ import { type MailDeepLink, } from "@/lib/deep-links"; import { consumePendingDeepLink } from "@/lib/deep-link-handoff"; -import { onProMailNavigation } from "@/lib/pro-mail-navigation"; import { useProInterfaceActive } from "@/components/pro/pro-interface-redirect"; import type { QuoteHeader } from "@/lib/plugin-types"; @@ -2417,26 +2416,6 @@ export function MailApp({ linkSegments }: MailAppProps = {}) { } }; - // Pro email tabs render their own sidebar; clicking a folder/tag there - // publishes a navigation request that this (always-mounted) embedded - // MailApp serves with its own selection handlers. Refs, not deps: the - // handlers close over client/mailboxes and are rebuilt every render. - const proNavHandlersRef = useRef({ handleMailboxSelect, handleAccountMailboxSelect, handleTagSelect }); - proNavHandlersRef.current = { handleMailboxSelect, handleAccountMailboxSelect, handleTagSelect }; - useEffect(() => { - if (!isEmbedded) return; - return onProMailNavigation((request) => { - const handlers = proNavHandlersRef.current; - if (request.kind === 'tag') { - void handlers.handleTagSelect(request.keywordId); - } else if (request.accountId) { - void handlers.handleAccountMailboxSelect(request.accountId, request.mailboxId); - } else { - void handlers.handleMailboxSelect(request.mailboxId); - } - }); - }, [isEmbedded]); - const handleUnreadFilterClick = async (mailboxId: string) => { const isTogglingOff = selectedMailbox === mailboxId && searchFilters.isUnread === true; diff --git a/components/pro/pro-email-tab-body.tsx b/components/pro/pro-email-tab-body.tsx index a7e8826e2..d4aafb73f 100644 --- a/components/pro/pro-email-tab-body.tsx +++ b/components/pro/pro-email-tab-body.tsx @@ -3,20 +3,15 @@ import { useCallback, useEffect, useState, useMemo, useRef } from "react"; import { useTranslations } from "next-intl"; import { EmailViewer } from "@/components/email/email-viewer"; -import { Sidebar } from "@/components/layout/sidebar"; -import { ErrorBoundary, EmailViewerErrorFallback, SidebarErrorFallback } from "@/components/error"; +import { ErrorBoundary, EmailViewerErrorFallback } from "@/components/error"; import { useAuthStore } from "@/stores/auth-store"; import { useEmailStore } from "@/stores/email-store"; import { useIdentityStore } from "@/stores/identity-store"; -import { useUIStore } from "@/stores/ui-store"; import { useProMultiAccountIdentities } from "@/hooks/use-pro-multi-account-identities"; -import { useDeviceDetection } from "@/hooks/use-media-query"; import { findDraftIdentityId } from "@/lib/reply-identity"; import { useSettingsStore } from "@/stores/settings-store"; import { toast } from "@/stores/toast-store"; import { useProTabStore, type ProEmailTabData, type ProReplyContext } from "@/stores/pro-tab-store"; -import { requestProMailNavigation } from "@/lib/pro-mail-navigation"; -import { DragDropProvider } from "@/contexts/drag-drop-context"; import type { Email } from "@/lib/jmap/types"; import type { IJMAPClient } from "@/lib/jmap/client-interface"; import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix"; @@ -396,28 +391,15 @@ export function ProEmailView({ emailId, client: clientOverride, accountId, onLoa } /** - * Renders a single email in its own Pro tab, Roundcube-style: the folder - * sidebar on the left (wide panes only) with the message filling the rest - - * no list pane. Clicking a folder or tag in that sidebar steers the shared - * Mail tab (via the pro-mail-navigation bus) and focuses it. + * Renders a single email in its own Pro tab: a fullscreen mail view - just + * the message, no folder sidebar and no list pane. */ export function ProEmailTabBody({ tabId, data }: ProEmailTabBodyProps) { const closeTab = useProTabStore((s) => s.closeTab); - const openTab = useProTabStore((s) => s.openTab); const updateTabTitle = useProTabStore((s) => s.updateTabTitle); - const mailboxes = useEmailStore((s) => s.mailboxes); - const accountMailboxes = useEmailStore((s) => s.accountMailboxes); - const sidebarWidth = useUIStore((s) => s.sidebarWidth); - const sidebarCollapsed = useUIStore((s) => s.sidebarCollapsed); - // Pane-scoped: a narrow split pane drops the sidebar and keeps the mail. - const { isMobile, isTablet } = useDeviceDetection(); - const showSidebar = !isMobile && !isTablet; - - const [loadedEmail, setLoadedEmail] = useState(null); const handleLoaded = useCallback((email: Email) => { - setLoadedEmail(email); if (email.subject) updateTabTitle(tabId, email.subject); }, [tabId, updateTabTitle]); @@ -432,59 +414,13 @@ export function ProEmailTabBody({ tabId, data }: ProEmailTabBodyProps) { return mb?.isShared ? mb.accountId : undefined; }, [mailboxes, data.mailboxId]); - // Highlight the folder the message lives in; fall back to the mailbox the - // tab was opened from (may be null for drag/drop-opened tabs). - const highlightedMailbox = useMemo(() => { - if (loadedEmail?.mailboxIds) { - const inList = Object.keys(loadedEmail.mailboxIds).find((id) => mailboxes.some((m) => m.id === id)); - if (inList) return inList; - } - return data.mailboxId ?? ''; - }, [loadedEmail, mailboxes, data.mailboxId]); - - const navigateToFolder = useCallback((accountId: string | null, mailboxId: string) => { - openTab('mail'); - requestProMailNavigation({ kind: 'mailbox', accountId, mailboxId }); - }, [openTab]); - - const navigateToTag = useCallback((keywordId: string | null) => { - openTab('mail'); - requestProMailNavigation({ kind: 'tag', keywordId }); - }, [openTab]); - return ( -
- {showSidebar && ( -
- - {/* The sidebar's folder rows are email-drop targets and read the - drag context unconditionally - give them their own provider - (MailApp's lives in a different tab body). */} - - navigateToFolder(null, mailboxId)} - onTagSelect={navigateToTag} - multiAccountMode - accountMailboxes={accountMailboxes} - viewingAccountId={null} - onAccountMailboxSelect={navigateToFolder} - /> - - -
- )} - -
+ ); } diff --git a/lib/pro-mail-navigation.ts b/lib/pro-mail-navigation.ts deleted file mode 100644 index 463eea45d..000000000 --- a/lib/pro-mail-navigation.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Navigation requests into the Pro shell's Mail tab from surfaces outside it. - * - * A Pro email tab renders its own folder sidebar (Roundcube-style mail view); - * clicking a folder there must steer the *Mail tab's* list. The Mail tab's - * `MailApp` owns all the folder-selection logic (unified views, scheduled, - * cross-account, keeping searches applied, ...), so instead of duplicating - * it, the email tab focuses the Mail tab and publishes a request here that - * the embedded MailApp handles with its own selection handlers. - * - * The deep-link handoff (lib/deep-link-handoff.ts) can't serve this: it is - * consumed once on mount, and in the Pro shell the Mail tab's MailApp stays - * mounted for the whole session. - */ -export type ProMailNavigationRequest = - | { - kind: 'mailbox'; - /** Connected-account id owning the folder; null = the active account. */ - accountId: string | null; - mailboxId: string; - } - | { kind: 'tag'; keywordId: string | null }; - -type Listener = (request: ProMailNavigationRequest) => void; - -const listeners = new Set(); - -export function onProMailNavigation(listener: Listener): () => void { - listeners.add(listener); - return () => { - listeners.delete(listener); - }; -} - -/** Publish a request. Returns false when no Mail tab is listening. */ -export function requestProMailNavigation(request: ProMailNavigationRequest): boolean { - if (listeners.size === 0) return false; - for (const listener of [...listeners]) listener(request); - return true; -}