Add compositor scrolling experiment

Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757
2026-07-09 16:09:09 -04:00
parent 46613dccec
commit 56e3b8a146
4 changed files with 50 additions and 4 deletions

View File

@@ -96,7 +96,10 @@ const LazySettingsScreen = React.lazy(async () => {
export function AppShell() {
useWebviewZoomShortcuts();
useTauriWindowDrag();
useWebviewScrollBoundaryLock();
const compositorTimelineScrollEnabled = useFeatureEnabled(
"compositorTimelineScroll",
);
useWebviewScrollBoundaryLock(!compositorTimelineScrollEnabled);
const workspacesHook = useWorkspaces();
const workspaceRailEnabled = useFeatureEnabled("workspaceRail");

View File

@@ -80,8 +80,12 @@ function isConversationScroller(element: HTMLElement) {
* remain; every other boundary — including all horizontal ones — is locked and
* cannot chain to the viewport.
*/
export function useWebviewScrollBoundaryLock() {
export function useWebviewScrollBoundaryLock(enabled = true) {
React.useEffect(() => {
if (!enabled) {
return;
}
function handleWheel(event: WheelEvent) {
if (event.defaultPrevented || event.ctrlKey) {
return;
@@ -137,5 +141,5 @@ export function useWebviewScrollBoundaryLock() {
return () => {
window.removeEventListener("wheel", handleWheel, { capture: true });
};
}, []);
}, [enabled]);
}

View File

@@ -28,7 +28,38 @@ async function dispatchWheelPrevented(
}
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
// Boundary-lock contracts exercise the stable/default path. Most E2E suites
// opt every preview feature in, but compositorTimelineScroll intentionally
// removes this listener so trackpad scrolling can stay off the main thread.
await installMockBridge(page, undefined, { seedPreviewFeatures: false });
});
test("compositor timeline scrolling leaves timeline wheel events unhandled", async ({
page,
}) => {
await page.addInitScript(() => {
localStorage.setItem(
"buzz-feature-overrides-v1",
JSON.stringify({ compositorTimelineScroll: true }),
);
});
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("message-timeline")).toBeVisible();
// The diagnostic path removes the global non-passive listener rather than
// merely returning early for conversations. The fixed-height shell and CSS
// overscroll backstop remain; JS no longer blocks timeline scrolling.
await expect(
dispatchWheelPrevented(page, '[data-testid="message-timeline"]', {
deltaY: -120,
}),
).resolves.toBe(false);
await expect(
dispatchWheelPrevented(page, '[data-testid="message-timeline"]', {
deltaX: -120,
}),
).resolves.toBe(false);
});
test("locks viewport rubber-band outside conversation scrollers", async ({

View File

@@ -40,6 +40,14 @@
"platforms": [
"desktop"
]
},
{
"id": "compositorTimelineScroll",
"name": "Compositor timeline scrolling",
"description": "Lets the webview handle timeline trackpad gestures without a global blocking wheel listener",
"platforms": [
"desktop"
]
}
]
}