diff --git a/.dockerignore b/.dockerignore index 35a0ac36..771c8d7d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,9 +5,7 @@ node_modules .env* !.env.example !.env.dev.example -.claude/ scripts/ TODO.md -CLAUDE.md *.md !README.md diff --git a/.gitignore b/.gitignore index 7ef1ebbe..76d65f17 100644 --- a/.gitignore +++ b/.gitignore @@ -42,9 +42,6 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -# claude code -.claude/ - # settings sync data /data/ diff --git a/app/[locale]/calendar/page.tsx b/app/[locale]/calendar/page.tsx index 12722d99..398abaa2 100644 --- a/app/[locale]/calendar/page.tsx +++ b/app/[locale]/calendar/page.tsx @@ -7,7 +7,7 @@ import { Plus } from "lucide-react"; import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, addMonths, subMonths, addWeeks, subWeeks, addDays, subDays, - format, parseISO, + startOfDay, format, parseISO, } from "date-fns"; import { useCalendarStore } from "@/stores/calendar-store"; import { isCalendarViewMode } from "@/stores/calendar-store"; @@ -156,11 +156,15 @@ export default function CalendarPage() { start: format(d, "yyyy-MM-dd'T'00:00:00"), end: format(d, "yyyy-MM-dd'T'23:59:59"), }; - case "agenda": + case "agenda": { + // Agenda always starts from today at the earliest + const today = startOfDay(new Date()); + const agendaStart = d >= today ? d : today; return { - start: format(d, "yyyy-MM-dd'T'00:00:00"), - end: format(addDays(d, 30), "yyyy-MM-dd'T'23:59:59"), + start: format(agendaStart, "yyyy-MM-dd'T'00:00:00"), + end: format(addDays(agendaStart, 30), "yyyy-MM-dd'T'23:59:59"), }; + } } }, [selectedDate, normalizedViewMode, firstDayOfWeek]); diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 41b1a8de..520fbb56 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -816,13 +816,13 @@ export default function Home() { }; }, []); - const handleDownloadAttachment = async (blobId: string, name: string, type?: string) => { + const handleDownloadAttachment = async (blobId: string, name: string, type?: string, forceDownload?: boolean) => { if (!client) return; try { const { mailAttachmentAction } = useSettingsStore.getState(); - if (mailAttachmentAction === 'preview' && isFilePreviewable(name, type)) { + if (!forceDownload && mailAttachmentAction === 'preview' && isFilePreviewable(name, type)) { setPreviewAttachment({ blobId, name, type }); return; } diff --git a/app/globals.css b/app/globals.css index f6649424..eac64432 100644 --- a/app/globals.css +++ b/app/globals.css @@ -20,6 +20,8 @@ --color-accent-foreground: #1e40af; --color-destructive: #ef4444; --color-destructive-foreground: #ffffff; + --color-popover: #ffffff; + --color-popover-foreground: #0f172a; /* Settings variables */ --font-size-base: 16px; @@ -50,6 +52,8 @@ --color-accent-foreground: #dbeafe; --color-destructive: #ef4444; --color-destructive-foreground: #fafafa; + --color-popover: #1c1c1c; + --color-popover-foreground: #fafafa; } @theme inline { @@ -68,6 +72,8 @@ --color-accent-foreground: var(--color-accent-foreground); --color-destructive: var(--color-destructive); --color-destructive-foreground: var(--color-destructive-foreground); + --color-popover: var(--color-popover); + --color-popover-foreground: var(--color-popover-foreground); } * { diff --git a/components/calendar/calendar-agenda-view.tsx b/components/calendar/calendar-agenda-view.tsx index 412927be..e8d1c391 100644 --- a/components/calendar/calendar-agenda-view.tsx +++ b/components/calendar/calendar-agenda-view.tsx @@ -1,8 +1,8 @@ "use client"; -import { useMemo } from "react"; +import { useMemo, useRef, useEffect, useCallback } from "react"; import { useTranslations, useFormatter } from "next-intl"; -import { format, parseISO, isToday, isTomorrow } from "date-fns"; +import { format, parseISO, isToday, isTomorrow, startOfDay } from "date-fns"; import { Calendar as CalendarIcon, MapPin, Users } from "lucide-react"; import { cn } from "@/lib/utils"; import { parseDuration, getEventColor } from "./event-card"; @@ -27,6 +27,7 @@ interface DayGroup { } export function CalendarAgendaView({ + selectedDate, events, calendars, onSelectEvent, @@ -43,6 +44,9 @@ export function CalendarAgendaView({ return map; }, [calendars]); + const todayRef = useRef(null); + const scrollContainerRef = useRef(null); + const grouped = useMemo(() => { const sorted = [...events].sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime() @@ -69,10 +73,38 @@ export function CalendarAgendaView({ } catch { /* skip invalid dates */ } }); + // Always include today's date in the groups so the view has a "Today" anchor + const todayKey = format(new Date(), "yyyy-MM-dd"); + if (!groupMap.has(todayKey)) { + const todayGroup = { date: startOfDay(new Date()), dateKey: todayKey, events: [] as CalendarEvent[] }; + groupMap.set(todayKey, todayGroup); + groups.push(todayGroup); + } + groups.sort((a, b) => a.date.getTime() - b.date.getTime()); return groups; }, [events]); + // Auto-scroll to today's section on mount and when selectedDate changes to today + const scrollToToday = useCallback(() => { + if (todayRef.current) { + todayRef.current.scrollIntoView({ block: "start" }); + } + }, []); + + useEffect(() => { + // Scroll to today on mount + const frame = requestAnimationFrame(scrollToToday); + return () => cancelAnimationFrame(frame); + }, [scrollToToday]); + + useEffect(() => { + // Scroll to today when selectedDate changes to today + if (isToday(selectedDate)) { + scrollToToday(); + } + }, [selectedDate, scrollToToday]); + const formatDateHeader = (date: Date): string => { if (isToday(date)) return t("events.today_header"); if (isTomorrow(date)) return t("events.tomorrow_header"); @@ -86,19 +118,10 @@ export function CalendarAgendaView({ return format(date, "HH:mm"); }; - if (grouped.length === 0) { - return ( -
- -

{t("events.no_events")}

-
- ); - } - return ( -
+
{grouped.map((group) => ( -
+
+ {group.events.length === 0 ? ( +
+ {t("events.no_events")} +
+ ) : (
{group.events.map((ev) => { const calId = getPrimaryCalendarId(ev); @@ -176,6 +204,7 @@ export function CalendarAgendaView({ ); })}
+ )}
))}
diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index fec4f7ce..b4ccd9d3 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -105,7 +105,7 @@ interface EmailViewerProps { onToggleStar?: () => void; onMarkAsRead?: (emailId: string, read: boolean) => void; onSetColorTag?: (emailId: string, color: string | null) => void; - onDownloadAttachment?: (blobId: string, name: string, type?: string) => void; + onDownloadAttachment?: (blobId: string, name: string, type?: string, forceDownload?: boolean) => void; onQuickReply?: (body: string) => Promise; onMarkAsSpam?: () => void; onUndoSpam?: () => void; @@ -149,6 +149,42 @@ const getFileIcon = (name?: string, type?: string) => { return File; }; +const MIME_TYPE_LABELS: Record = { + 'application/pdf': 'Document.pdf', + 'application/zip': 'Archive.zip', + 'application/x-zip-compressed': 'Archive.zip', + 'application/gzip': 'Archive.gz', + 'application/x-rar-compressed': 'Archive.rar', + 'application/x-7z-compressed': 'Archive.7z', + 'application/msword': 'Document.doc', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Document.docx', + 'application/vnd.ms-excel': 'Spreadsheet.xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Spreadsheet.xlsx', + 'application/vnd.ms-powerpoint': 'Presentation.ppt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'Presentation.pptx', + 'text/plain': 'Text.txt', + 'text/html': 'Document.html', + 'text/csv': 'Data.csv', + 'application/json': 'Data.json', + 'application/xml': 'Data.xml', + 'application/octet-stream': 'Attachment', + 'message/rfc822': 'Email.eml', +}; + +const getAttachmentDisplayName = (name: string | null | undefined, mimeType?: string): string => { + if (name) return name; + if (mimeType) { + const label = MIME_TYPE_LABELS[mimeType.toLowerCase()]; + if (label) return label; + const sub = mimeType.split('/')[1]; + if (sub) { + const clean = sub.replace(/^x-/, '').replace(/^vnd\./, ''); + return `Attachment.${clean}`; + } + } + return 'Attachment'; +}; + const getCurrentColor = (keywords: Record | undefined) => { if (!keywords) return null; for (const key of Object.keys(keywords)) { @@ -837,6 +873,7 @@ export function EmailViewer({ const tFiles = useTranslations('files'); const externalContentPolicy = useSettingsStore((state) => state.externalContentPolicy); const mailAttachmentAction = useSettingsStore((state) => state.mailAttachmentAction); + const attachmentPosition = useSettingsStore((state) => state.attachmentPosition); const addTrustedSender = useSettingsStore((state) => state.addTrustedSender); const isSenderTrusted = useSettingsStore((state) => state.isSenderTrusted); const emailKeywords = useSettingsStore((state) => state.emailKeywords); @@ -864,6 +901,8 @@ export function EmailViewer({ const { identities, client } = useAuthStore(); const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const [showFullHeaders, setShowFullHeaders] = useState(false); + const [showAllBesideAttachments, setShowAllBesideAttachments] = useState(false); + const [showAllMobileAttachments, setShowAllMobileAttachments] = useState(false); const [allowExternalContent, setAllowExternalContent] = useState(false); const [hasBlockedContent, setHasBlockedContent] = useState(false); const [cidBlobUrls, setCidBlobUrls] = useState>({}); @@ -2394,6 +2433,44 @@ export function EmailViewer({ setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000); }, [mailAttachmentAction, onDownloadAttachment]); + const handleEffectiveAttachmentDownload = useCallback((attachment: EffectiveAttachment) => { + if (attachment.blobId && onDownloadAttachment) { + onDownloadAttachment(attachment.blobId, attachment.name || 'download', attachment.type, true); + return; + } + + if (attachment.tnefData) { + const buffer = attachment.tnefData.buffer.slice( + attachment.tnefData.byteOffset, + attachment.tnefData.byteOffset + attachment.tnefData.byteLength, + ) as ArrayBuffer; + const blob = new Blob([buffer], { type: attachment.type || 'application/octet-stream' }); + const objectUrl = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = objectUrl; + anchor.download = attachment.name || 'download'; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + setTimeout(() => URL.revokeObjectURL(objectUrl), 60000); + return; + } + + if (!attachment.decryptedAttachment) return; + const bytes = getAttachmentContentBytes(attachment.decryptedAttachment); + if (!bytes || bytes.byteLength === 0) return; + const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer; + const blob = new Blob([buffer], { type: attachment.type || 'application/octet-stream' }); + const objectUrl = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = objectUrl; + anchor.download = attachment.name || 'download'; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000); + }, [onDownloadAttachment]); + // Iframe for rendering HTML emails true-to-life const iframeRef = useRef(null); @@ -2913,6 +2990,21 @@ export function EmailViewer({ + {/* Dark/light mode toggle for HTML emails */} + {effectiveEmailContent.isHtml && ( + + )} + {/* More menu — click-based */}
+ {/* Overflow: dark/light mode toggle */} + {effectiveEmailContent.isHtml && ( + + )}
{/* Export email */} + {effectiveEmailContent.isHtml && ( + + )}
@@ -3408,13 +3524,14 @@ export function EmailViewer({ name={sender?.name} email={sender?.email} size="lg" - className="shadow-sm w-12 h-12 group-hover:ring-2 group-hover:ring-primary/30 transition-all" + className="shadow-sm w-10 h-10 group-hover:ring-2 group-hover:ring-primary/30 transition-all" /> -
- {/* Sender line with email and badges */} -
+
+
+ {/* Row 1: Sender name + badges */} +
- {sender?.email && ( -
- {sender.email} - {shouldShowUnsubBanner && listHeaders?.listUnsubscribe && ( - { - const messageId = email?.messageId || ''; - const newSet = new Set(dismissedUnsubBanners).add(messageId); - setDismissedUnsubBanners(newSet); - localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet])); - }} - /> - )} -
- )} -
- {/* Date and size on the right */} -
-
- {formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })} -
- {email.size > 0 && ( -
- {formatFileSize(email.size)} -
- )} - {effectiveEmailContent.isHtml && ( - + {/* Email address under name */} + {sender?.email && sender?.name && ( +
{sender.email}
)}
- {/* Recipient section - separate line */} -
+ {/* Row 2: Recipients + Show details */} +
{email.to && email.to.length > 0 && ( -
- {t('recipient_to_prefix')} + <> + {t('recipient_to_prefix')} {renderClickableRecipients(email.to, currentUserEmail, t, handleViewContactSidebar)} {email.to.length > 2 && ( )} -
+ )} - {email.cc && email.cc.length > 0 && ( -
- CC: + <> + | + CC: {renderClickableRecipients(email.cc, currentUserEmail, t, handleViewContactSidebar)} {email.cc.length > 2 && ( - +{email.cc.length - 2} + +{email.cc.length - 2} )} -
+ )} - {email.bcc && email.bcc.length > 0 && ( -
- {t('bcc')}: + <> + | + {t('bcc')}: {renderClickableRecipients(email.bcc, currentUserEmail, t, handleViewContactSidebar)} {email.bcc.length > 2 && ( - +{email.bcc.length - 2} + +{email.bcc.length - 2} )} -
+ )} +
- {/* Details toggle - stays in place when expanded */} - - {/* Expandable Details */} {showFullHeaders && (
@@ -3893,46 +3987,248 @@ export function EmailViewer({
)} +
+ {/* Attachments on the right (beside-sender mode) */} + {attachmentPosition === 'beside-sender' && effectiveAttachments.length > 0 && ( +
+ {effectiveAttachments.slice(0, 2).map((attachment) => { + const FileIcon = getFileIcon(attachment.name || undefined, attachment.type); + const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type); + const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; + return ( +
+ + + {getAttachmentDisplayName(attachment.name, attachment.type)} + + + {formatFileSize(attachment.size)} + +
+ + {opensPreview && ( + + )} +
+
+ ); + })} + {effectiveAttachments.length > 2 && ( + + )} + {/* Floating popup for remaining attachments */} + {showAllBesideAttachments && effectiveAttachments.length > 2 && ( + <> +
setShowAllBesideAttachments(false)} /> +
+ {effectiveAttachments.slice(2).map((attachment) => { + const FileIcon = getFileIcon(attachment.name || undefined, attachment.type); + const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type); + const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; + return ( +
+ + + {getAttachmentDisplayName(attachment.name, attachment.type)} + + + {formatFileSize(attachment.size)} + +
+ + {opensPreview && ( + + )} +
+
+ ); + })} +
+ + )} +
+ )}
- {/* === ATTACHMENTS (integrated into header) === */} - {effectiveAttachments.length > 0 && ( -
-
+ {/* === ATTACHMENTS below header (below-header mode, desktop only) === */} + {attachmentPosition === 'below-header' && effectiveAttachments.length > 0 && ( +
+
{effectiveAttachments.map((attachment) => { const FileIcon = getFileIcon(attachment.name || undefined, attachment.type); const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type); const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; return ( - + {opensPreview && ( + + )}
- {opensPreview ? ( - - ) : ( - - )} - +
); })}
)} + {/* Mobile/Tablet Attachments */} + {effectiveAttachments.length > 0 && ( +
+
+ {effectiveAttachments.slice(0, 2).map((attachment) => { + const FileIcon = getFileIcon(attachment.name || undefined, attachment.type); + const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type); + const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; + return ( +
+ + + {getAttachmentDisplayName(attachment.name, attachment.type)} + + + {formatFileSize(attachment.size)} + +
+ + {opensPreview && ( + + )} +
+
+ ); + })} + {effectiveAttachments.length > 2 && ( + + )} + {showAllMobileAttachments && effectiveAttachments.length > 2 && ( + <> +
setShowAllMobileAttachments(false)} /> +
+ {effectiveAttachments.slice(2).map((attachment) => { + const FileIcon = getFileIcon(attachment.name || undefined, attachment.type); + const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type); + const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; + return ( +
+ + + {getAttachmentDisplayName(attachment.name, attachment.type)} + + + {formatFileSize(attachment.size)} + +
+ + {opensPreview && ( + + )} +
+
+ ); + })} +
+ + )} +
+
+ )} + {/* Mobile/Tablet Sender Info - scrolls with content */}
@@ -3949,8 +4245,8 @@ export function EmailViewer({ />
- {/* Mobile 2-line layout */} -
+ {/* Row 1: Sender name + badges */} +
-
-
- {sender?.email && sender?.name && ( - <> - {sender.email} - {shouldShowUnsubBanner && listHeaders?.listUnsubscribe && ( - { - const messageId = email?.messageId || ''; - const newSet = new Set(dismissedUnsubBanners).add(messageId); - setDismissedUnsubBanners(newSet); - localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet])); - }} - /> - )} - · - + {shouldShowUnsubBanner && listHeaders?.listUnsubscribe && ( + { + const messageId = email?.messageId || ''; + const newSet = new Set(dismissedUnsubBanners).add(messageId); + setDismissedUnsubBanners(newSet); + localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet])); + }} + /> )} +
+ {/* Email address under name */} + {sender?.email && sender?.name && ( +
{sender.email}
+ )} + {/* Row 2: Recipients */} +
{email.to && email.to.length > 0 && ( <> → {t('recipient_to_prefix')} {renderClickableRecipients(email.to, currentUserEmail, t, handleViewContactSidebar)} )} + {email.cc && email.cc.length > 0 && ( + <> + | + CC: + {renderClickableRecipients(email.cc, currentUserEmail, t, handleViewContactSidebar)} + {email.cc.length > 2 && ( + +{email.cc.length - 2} + )} + + )}
- {/* CC line (mobile - only if present) */} - {email.cc && email.cc.length > 0 && ( -
- CC: - {renderClickableRecipients(email.cc, currentUserEmail, t, handleViewContactSidebar)} - {email.cc.length > 2 && ( - +{email.cc.length - 2} - )} -
- )}
diff --git a/components/settings/email-settings.tsx b/components/settings/email-settings.tsx index ee6189bf..ed74e35e 100644 --- a/components/settings/email-settings.tsx +++ b/components/settings/email-settings.tsx @@ -24,6 +24,7 @@ export function EmailSettings() { emailsPerPage, externalContentPolicy, mailAttachmentAction, + attachmentPosition, emailAlwaysLightMode, archiveMode, trustedSenders, @@ -195,6 +196,17 @@ export function EmailSettings() { /> + + ()( } } catch (err) { debug.error(`Failed to restore client for ${accountId}:`, err); - accountStore.updateAccount(accountId, { - isConnected: false, - hasError: true, - errorMessage: err instanceof Error ? err.message : 'Connection failed', - }); - set({ isLoading: false }); - return; } } if (!targetClient) { - accountStore.updateAccount(accountId, { - isConnected: false, - hasError: true, - errorMessage: 'Unable to restore session', - }); + // Cannot restore — remove the stale account and redirect to login + evictAccount(accountId); + accountStore.removeAccount(accountId); + fetch(`/api/auth/session?slot=${targetAccount.cookieSlot}`, { method: 'DELETE' }).catch(() => {}); + + // Restore the previous account if still available + if (state.activeAccountId && state.activeAccountId !== accountId) { + const prevClient = clients.get(state.activeAccountId); + const prevAccount = accountStore.getAccountById(state.activeAccountId); + if (prevClient && prevAccount) { + restoreAccount(state.activeAccountId); + accountStore.setActiveAccount(state.activeAccountId); + set({ + isLoading: false, + serverUrl: prevAccount.serverUrl, + username: prevAccount.username, + client: prevClient, + authMode: prevAccount.authMode, + rememberMe: prevAccount.rememberMe, + connectionLost: false, + activeAccountId: state.activeAccountId, + }); + return; + } + } + set({ isLoading: false }); + // Redirect to login so the user can re-authenticate + replaceWindowLocation(getLocaleLoginPath()); return; } @@ -865,11 +881,11 @@ export const useAuthStore = create()( } } catch (err) { debug.error(`Failed to restore account ${account.id}:`, err); - accountStore.updateAccount(account.id, { - isConnected: false, - hasError: true, - errorMessage: err instanceof Error ? err.message : 'Restore failed', - }); + // Remove unrestorable accounts so the user is prompted to log in + // again rather than seeing a stale error entry forever. + evictAccount(account.id); + accountStore.removeAccount(account.id); + fetch(`/api/auth/session?slot=${account.cookieSlot}`, { method: 'DELETE' }).catch(() => {}); } } diff --git a/stores/calendar-store.ts b/stores/calendar-store.ts index d3a4f3ee..17f71f10 100644 --- a/stores/calendar-store.ts +++ b/stores/calendar-store.ts @@ -655,6 +655,7 @@ export const useCalendarStore = create()( return { ...mergedState, + selectedDate: new Date(), viewMode: getSafeCalendarViewMode(mergedState.viewMode), }; }, diff --git a/stores/settings-store.ts b/stores/settings-store.ts index 5a0399ec..ff8eca7c 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -29,6 +29,7 @@ export type TimeFormat = '12h' | '24h'; export type FirstDayOfWeek = 0 | 1; // 0 = Sunday, 1 = Monday export type ExternalContentPolicy = 'ask' | 'block' | 'allow'; export type MailAttachmentAction = 'preview' | 'download'; +export type AttachmentPosition = 'beside-sender' | 'below-header'; export type ToolbarPosition = 'top' | 'below-subject'; export type ArchiveMode = 'single' | 'year' | 'month'; @@ -92,6 +93,7 @@ interface SettingsState { emailsPerPage: number; externalContentPolicy: ExternalContentPolicy; mailAttachmentAction: MailAttachmentAction; + attachmentPosition: AttachmentPosition; emailAlwaysLightMode: boolean; // Always render email content in light mode archiveMode: ArchiveMode; // How to organize archived emails: single folder, by year, or by year+month @@ -186,6 +188,7 @@ const DEFAULT_SETTINGS = { emailsPerPage: 50, externalContentPolicy: 'ask' as ExternalContentPolicy, mailAttachmentAction: 'preview' as MailAttachmentAction, + attachmentPosition: 'beside-sender' as AttachmentPosition, emailAlwaysLightMode: false, archiveMode: 'single' as ArchiveMode, @@ -271,6 +274,7 @@ export const useSettingsStore = create()( emailsPerPage: state.emailsPerPage, externalContentPolicy: state.externalContentPolicy, mailAttachmentAction: state.mailAttachmentAction, + attachmentPosition: state.attachmentPosition, archiveMode: state.archiveMode, trustedSenders: state.trustedSenders, autoSaveDraftInterval: state.autoSaveDraftInterval,