diff --git a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
index b2e6fcaaaf402..10bf817b7363f 100644
--- a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
+++ b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
@@ -29,6 +29,7 @@ describe("HistorySwipeNavigation", () => {
["a swipe that started in a closed dialog keeps the thread", "closed-dialog", 0, -130, false],
] as const)("%s", async (_name, scroller, scrollLeft, deltaX, navigates) => {
navigateHistory.mockReset();
+ using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("MacIntel");
await using screen = await render();
const panel = screen.container.appendChild(document.createElement("div"));
const root = scroller.startsWith("shadow") ? panel.attachShadow({ mode: "open" }) : panel;
@@ -58,4 +59,16 @@ describe("HistorySwipeNavigation", () => {
expect(navigateHistory).toHaveBeenCalledTimes(navigates ? 1 : 0);
});
+
+ it("a horizontal mouse wheel tick off macOS keeps the thread", async () => {
+ navigateHistory.mockReset();
+ using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("Win32");
+ await using screen = await render();
+ const content = screen.container.appendChild(document.createElement("div"));
+ content.style.cssText = "height: 20px; width: 100px";
+
+ swipe(content, -120);
+
+ expect(navigateHistory).not.toHaveBeenCalled();
+ });
});
diff --git a/apps/web/src/components/HistorySwipeNavigation.tsx b/apps/web/src/components/HistorySwipeNavigation.tsx
index 767b4285c696f..e1adc04491a91 100644
--- a/apps/web/src/components/HistorySwipeNavigation.tsx
+++ b/apps/web/src/components/HistorySwipeNavigation.tsx
@@ -1,7 +1,7 @@
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { useEffect, useState } from "react";
-import { cn } from "../lib/utils";
+import { cn, isMacPlatform } from "../lib/utils";
import { canNavigateHistory, navigateHistory, type HistoryDirection } from "../navigationHistory";
const SWIPE_TRIGGER_DISTANCE = 120;
@@ -88,12 +88,16 @@ export function isMomentumTail(deltas: readonly number[]): boolean {
);
}
-/** Two-finger horizontal trackpad swipes go back and forward, like a browser. */
+/**
+ * Two-finger horizontal trackpad swipes go back and forward, like a browser.
+ * macOS only: on Windows and Linux a mouse tilt wheel is indistinguishable from a trackpad swipe.
+ */
export function HistorySwipeNavigation() {
const [swipe, setSwipe] = useState(null);
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- window wheel and desktop gesture listeners track trackpad swipes
useEffect(() => {
+ if (!isMacPlatform(navigator.platform)) return;
let gesture: SwipeGesture | null = null;
const hideIndicator = () =>
setSwipe((current) => (current === null ? null : { ...current, tracking: false }));