diff --git a/components/email/__tests__/reading-mode-toggle.test.tsx b/components/email/__tests__/reading-mode-toggle.test.tsx
new file mode 100644
index 000000000..091740c7b
--- /dev/null
+++ b/components/email/__tests__/reading-mode-toggle.test.tsx
@@ -0,0 +1,102 @@
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect, vi } from 'vitest';
+import type { Email, Mailbox } from '@/lib/jmap/types';
+
+// #964: the reading-mode (Sun/Moon) toggle used to mount only once the body
+// had loaded and turned out to be HTML. That late mount changed the toolbar
+// width and re-ran the overflow calculation, so the other buttons jumped.
+// The toggle must be in the DOM from the start, merely disabled.
+
+vi.mock('@/hooks/use-media-query', () => ({
+ useDeviceDetection: () => ({ isMobile: false, isTablet: false, isDesktop: true }),
+ useMediaQuery: () => false,
+}));
+
+vi.mock('@/components/tour/tour-provider', () => ({
+ useTour: () => ({ activeTour: null, startTour: vi.fn(), endTour: vi.fn(), registerStep: vi.fn() }),
+}));
+
+vi.mock('@/components/plugins/plugin-slot', () => ({
+ PluginSlot: () => null,
+}));
+
+vi.mock('@/hooks/use-plugin-slot-offers', () => ({
+ usePluginSlotOffers: () => [],
+}));
+
+class NoopResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+}
+vi.stubGlobal('ResizeObserver', NoopResizeObserver);
+
+const { EmailViewer } = await import('../email-viewer');
+
+const mailboxes: Mailbox[] = [
+ { id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, myRights: { mayAddItems: true } },
+] as unknown as Mailbox[];
+
+function makeEmail(overrides: Partial
- {/* Dark/light mode toggle for HTML emails */}
- {effectiveEmailContent.isHtml && (
+ {/* Dark/light mode toggle for HTML emails. Always mounted: `isHtml`
+ flips from false to true once the body arrives, and mounting the
+ button then changes the toolbar width and re-runs the overflow
+ calculation, so the other buttons jump. Disable it instead. (#964) */}
- )}
{/* Fullscreen toggle - hidden on mobile (already fullscreen there).
Never overflows into the More menu: in fullscreen this button is