diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea7f96c..fa70b9dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 1.4.5 (2026-03-20) + +### Features + +- **Calendar**: Add prev/next navigation buttons and date label to desktop calendar toolbar +- **Calendar**: Add pending event preview functionality to calendar views and event modal +- **Calendar**: Add setting to show event start time in month view +- **Contacts**: Implement pagination for fetching contacts with maxObjectsInGet capability +- **Email**: Add attachment position setting in email settings +- **Layout**: Add mobile visibility toggle for sidebar apps +- **Error**: Add NotFound component to handle 404 errors and redirect unauthenticated users + +### Fixes + +- **Auth**: Enhance account switching logic and clear stores on account change +- **Auth**: Improve account restoration logic and handle stale accounts +- **Auth**: Improve draft handling in email composer and enhance session cookie verification +- **Calendar**: Expand recurring events in CalendarEvent/query so individual occurrences are returned (#65) +- **Calendar**: Validate event start field when fetching calendar events +- **Calendar**: Auto-scroll agenda view to today's events and include today's date in groups +- **Calendar**: Correct JSX syntax in CalendarToolbar component +- **Dependencies**: Update flatted to 3.4.2 +- **DevOps**: Use native ARM runners instead of QEMU for Docker builds +- **DevOps**: Enhance health check with detailed memory diagnostics and stable liveness probe + ## 1.4.4 (2026-03-19) ### Features diff --git a/VERSION b/VERSION index 1c99cf0e..e516bb9d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.4 +1.4.5 diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 3f1b5c8f..520fbb56 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -70,12 +70,9 @@ export default function Home() { const [isLoadingConversation, setIsLoadingConversation] = useState(false); const [previewAttachment, setPreviewAttachment] = useState<{ blobId: string; name: string; type?: string } | null>(null); const markAsReadTimeoutRef = useRef(null); - const { isAuthenticated, client, logout, checkAuth, isLoading: authLoading, connectionLost, activeAccountId } = useAuthStore(); + const { isAuthenticated, client, logout, checkAuth, isLoading: authLoading, connectionLost } = useAuthStore(); const { identities } = useIdentityStore(); - // Track account switches to force data reload - const lastLoadedAccountRef = useRef(null); - // Mobile/tablet responsive hooks const { isMobile, isTablet } = useDeviceDetection(); const { activeView, sidebarOpen, setSidebarOpen, setActiveView, tabletListVisible, setTabletListVisible, sidebarWidth, emailListWidth, setSidebarWidth, setEmailListWidth, persistColumnWidths, sidebarCollapsed, resetSidebarWidth, resetEmailListWidth } = useUIStore(); @@ -292,19 +289,9 @@ export default function Home() { } }, [initialCheckDone, isAuthenticated, authLoading, router]); - // Load mailboxes and emails when authenticated - // Re-fetches on initial load (mailboxes empty) or when account switches + // Load mailboxes and emails when authenticated (only if not already loaded) useEffect(() => { - const accountChanged = lastLoadedAccountRef.current !== null && lastLoadedAccountRef.current !== activeAccountId; - if (isAuthenticated && client && (mailboxes.length === 0 || accountChanged)) { - lastLoadedAccountRef.current = activeAccountId; - - // Clear stale selected email from the previous account so the viewer - // doesn't flash old content while fresh data loads. - if (accountChanged) { - selectEmail(null); - } - + if (isAuthenticated && client && mailboxes.length === 0) { const loadData = async () => { try { // First fetch mailboxes and quota (inbox will be auto-selected in fetchMailboxes) @@ -350,18 +337,15 @@ export default function Home() { } }; loadData(); - } else if (isAuthenticated && client && lastLoadedAccountRef.current === null) { - // First render with existing data (e.g. restored from snapshot) — just record the account - lastLoadedAccountRef.current = activeAccountId; } - // Cleanup push notifications on unmount or client change + // Cleanup push notifications on unmount return () => { if (client) { client.closePushNotifications(); } }; - }, [isAuthenticated, client, mailboxes.length, activeAccountId, fetchMailboxes, fetchEmails, fetchQuota, fetchTagCounts, handleStateChange, setPushConnected, selectEmail]); + }, [isAuthenticated, client, mailboxes.length, fetchMailboxes, fetchEmails, fetchQuota, fetchTagCounts, handleStateChange, setPushConnected]); // Auto-fetch full email content when an email is auto-selected (e.g. after delete/archive) useEffect(() => { @@ -487,9 +471,7 @@ export default function Home() { }; const handleEditDraft = (email?: Email) => { - // Guard: when used directly as an onClick handler, the click event is passed - // as the first argument. Detect this and fall back to selectedEmail. - const draft = (email && 'mailboxIds' in email) ? email : selectedEmail; + const draft = email || selectedEmail; if (!draft) return; const bodyText = draft.bodyValues ? Object.values(draft.bodyValues).map(v => v.value).join('\n') diff --git a/locales/en/common.json b/locales/en/common.json index 7c88f87a..d9b22049 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -137,8 +137,7 @@ "show_all": "All", "no_icons_found": "No icons found", "inline_badge": "Inline", - "tab_badge": "Tab", - "show_on_mobile": "Show on Mobile" + "tab_badge": "Tab" }, "email_list": { "no_emails": "No messages found", @@ -1827,9 +1826,7 @@ "notification_sound": "Notification sound", "notification_sound_desc": "Play a sound for calendar alerts", "invitation_parsing": "Parse email invitations", - "invitation_parsing_desc": "Detect calendar invitations in email attachments and show calendar actions", - "show_time_in_month_view": "Show start time in month view", - "show_time_in_month_view_desc": "Display the event start time alongside the title in month view" + "invitation_parsing_desc": "Detect calendar invitations in email attachments and show calendar actions" }, "days": { "monday": "Monday", diff --git a/package.json b/package.json index 7d9e5da4..07bbd6b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bulwark-webmail", - "version": "1.4.4", + "version": "1.4.5", "description": "Bulwark Webmail — a modern webmail client built for Stalwart Mail Server", "author": "Bulwark Webmail ", "license": "AGPL-3.0-only", diff --git a/stores/settings-store.ts b/stores/settings-store.ts index a680b024..ff8eca7c 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -45,7 +45,6 @@ export interface SidebarApp { url: string; icon: string; // Lucide icon name (e.g. 'Globe', 'Rss') openMode: 'tab' | 'inline'; // Open in new tab or embed inline - showOnMobile?: boolean; // Show in mobile bottom nav (default false) } // Available color palette for keywords @@ -107,9 +106,6 @@ interface SettingsState { sessionTimeout: number; // minutes (0 = never) trustedSenders: string[]; // Email addresses that can load external content - // Calendar - showTimeInMonthView: boolean; - // Calendar Notifications calendarNotificationsEnabled: boolean; calendarNotificationSound: boolean; @@ -205,9 +201,6 @@ const DEFAULT_SETTINGS = { sessionTimeout: 0, // Never trustedSenders: [] as string[], - // Calendar - showTimeInMonthView: false, - // Calendar Notifications calendarNotificationsEnabled: true, calendarNotificationSound: true, @@ -288,7 +281,6 @@ export const useSettingsStore = create()( sendConfirmation: state.sendConfirmation, defaultReplyMode: state.defaultReplyMode, sessionTimeout: state.sessionTimeout, - showTimeInMonthView: state.showTimeInMonthView, calendarNotificationsEnabled: state.calendarNotificationsEnabled, calendarNotificationSound: state.calendarNotificationSound, calendarInvitationParsingEnabled: state.calendarInvitationParsingEnabled,