Skip to content

fix(wheel): exclude Mac Chrome linear wheelDelta from legacy mouse detection#306

Merged
draedful merged 5 commits into
mainfrom
cursor/fix-mac-chrome-trackpad-wheel-intent
Jun 29, 2026
Merged

fix(wheel): exclude Mac Chrome linear wheelDelta from legacy mouse detection#306
draedful merged 5 commits into
mainfrom
cursor/fix-mac-chrome-trackpad-wheel-intent

Conversation

@draedful

@draedful draedful commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix fast trackpad swipes on Mac Chrome/YaBrowser being misclassified as zoom instead of pan
  • Exclude wheelDelta ≈ 3×deltaY (Mac Chrome trackpad pattern) from hasLegacyMouseWheelDelta detection
  • Add regression test reproducing YaBrowser trackpad swipe log

Test plan

  • npm test -- wheelIntent.test.ts (28 tests pass)
  • Verify fast trackpad swipe pans the graph in YaBrowser/Chrome on macOS
  • Verify Windows Chromium mouse wheel still zooms with MOUSE_WHEEL_BEHAVIOR=zoom

Made with Cursor

Summary by Sourcery

Adjust wheel intent resolution to better distinguish trackpad vs mouse input, prevent mid-gesture intent flips, and expose explicit wheel input device configuration, with updated docs, stories, and tests.

New Features:

  • Introduce wheelInputDevice setting and resolver option to explicitly classify wheel input as trackpad or mouse.
  • Add rapid-stream sticky intent rule to keep prior pan/zoom intent within fast wheel gesture streams.
  • Expose TWheelInputDevice type and wheelInputDevice setting through graph configuration and public API.

Bug Fixes:

  • Exclude Mac Chrome/YaBrowser linear wheelDelta (~3×deltaY) from legacy mouse wheel detection so fast trackpad swipes stay as pan instead of zoom.

Enhancements:

  • Refine wheel intent rules, priority order, and documentation to cover Mac Chrome edge cases and explicit device modes.
  • Extend wheel intent debug output with inputDevice and new signals, and update stories to control wheelInputDevice alongside MOUSE_WHEEL_BEHAVIOR.

Documentation:

  • Expand wheel intent and camera documentation with detailed wheelInputDevice mode explanations, decision flow, and Mac Chrome behavior guidance.

Tests:

  • Add tests covering Mac Chrome trackpad wheel pattern, rapid-stream sticky intent behavior, and explicit mouse/trackpad modes in the resolver.

…tection

Fast trackpad swipes on Mac Chrome/YaBrowser were misclassified as zoom because
wheelDelta ≈ 3×deltaY crossed the legacy threshold. Treat that ratio as trackpad.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sourcery-ai

sourcery-ai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refines wheel intent resolution to avoid treating Mac Chrome/YaBrowser linear wheelDelta trackpad scrolls as legacy mouse zoom, adds explicit wheel input device modes, introduces a rapid-stream sticky intent post-pass, and updates docs, tests, stories, and settings wiring accordingly.

File-Level Changes

Change Details Files
Exclude Mac Chrome/YaBrowser linear wheelDelta pattern from legacy mouse wheel detection so fast trackpad swipes stay pan.
  • Add tolerance-based detection of Mac Chrome linear wheelDelta ratio (~3× deltaY) and treat it as non-legacy.
  • Require legacy wheelDelta to be above a magnitude threshold and not match the Mac Chrome linear pattern before classifying as mouse.
  • Update debug signal description for hasLegacyMouseWheelDelta to clarify Mac Chrome 3× ratio exclusion.
src/utils/functions/wheelIntent.ts
docs/system/wheel-intent.md
Introduce explicit wheel input device modes (auto/mouse/trackpad) and wire them through resolver, settings, and public API.
  • Add TWheelInputDevice type and resolver options (inputDevice) to createWheelIntentResolver, defaulting to auto.
  • Implement I3:input-device-trackpad and I4:input-device-mouse branches that bypass ambiguous heuristics when inputDevice is set.
  • Extend TWheelIntentDebugEntry with inputDevice and emit it from debug logger.
  • Expose TWheelInputDevice from graphConfig and index, add wheelInputDevice to graph settings with default auto, and make GraphEditorSettings recreate the resolver when wheelInputDevice changes.
src/utils/functions/wheelIntent.ts
src/store/settings.ts
src/graphConfig.ts
src/index.ts
docs/system/wheel-intent.md
docs/system/camera.md
Add rapid-stream sticky intent (I5:sticky-stream) to prevent pan/zoom flipping within one continuous gesture.
  • Implement applyRapidStreamStickyIntent post-pass that keeps prior intent during rapid streams when a confident rule would flip intent.
  • Track lastRule alongside lastIntent and integrate sticky logic into resolver at the end of rule matching.
  • Document I5:sticky-stream behavior and rule ordering in wheel-intent docs and camera notes.
src/utils/functions/wheelIntent.ts
docs/system/wheel-intent.md
docs/system/camera.md
Extend tests to cover Mac Chrome trackpad behavior, sticky intent, and explicit wheel input device modes, and adjust timing assumptions.
  • Add makeMacChromeTrackpadWheelEvent helper modeling integer deltaY with wheelDelta ≈ 3×deltaY.
  • Add regression test that replays YaBrowser trackpad logs ensuring all ticks resolve to pan and are not marked legacy mouse.
  • Add multiple tests covering I5:sticky-stream behavior, including exemptions (pinch, I2) and last-intent interactions.
  • Add tests for inputDevice="trackpad" and inputDevice="mouse" on Mac Chrome-style events and for respecting MOUSE_WHEEL_BEHAVIOR when device is mouse.
  • Adjust existing mouse wheel tests to advance time between calls to avoid rapid-stream/burst interference.
src/utils/functions/wheelIntent.test.ts
Update documentation and Storybook examples to surface wheelInputDevice and new intent behavior for debugging and configuration.
  • Expand wheel-intent.md with wheelInputDevice modes section, gesture tables, configuration examples, and Mac Chrome edge-case guidance.
  • Clarify fractional vs integer PIXEL behavior, sticky-stream, and known limitations around ambiguous Mac Chrome input.
  • Update camera.md to describe wheelInputDevice, its interaction with MOUSE_WHEEL_BEHAVIOR, and link to wheel-intent docs.
  • Add wheelInputDevice control and wiring in mouseWheelBehaviorScroll and wheelIntentProbe stories, passing it into resolver and graph settings.
  • Extend WheelEventLogPanel to display wheelInputDevice alongside MOUSE_WHEEL_BEHAVIOR for captured events.
docs/system/wheel-intent.md
docs/system/camera.md
src/stories/examples/mouseWheelBehaviorScroll/mouseWheelBehaviorScroll.stories.tsx
src/stories/examples/wheelIntentProbe/wheelIntentProbe.stories.tsx
src/stories/examples/wheelIntentProbe/WheelEventLogPanel.tsx

Possibly linked issues

  • #(unknown): PR implements and documents configurable mouse wheel zoom/scroll behavior and adds wheelInputDevice, matching requested scroll configuration.
  • #(unknown): PR updates wheel intent to keep Mac trackpad vertical swipes as pan, matching the reported unexpected zoom behavior.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gravity-ui-bot

Copy link
Copy Markdown
Contributor

Preview is ready.

draedful and others added 2 commits June 29, 2026 15:15
Explicit trackpad/mouse/auto mode for ambiguous Mac Chrome input; I5:sticky-stream
prevents pan/zoom flips mid-gesture. Document camera behavior and Storybook controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes e2e CI webServer startup: tsc failed on metaKey in makeMacChromeTrackpadWheelEvent.

Co-authored-by: Cursor <cursoragent@cursor.com>
@draedful draedful marked this pull request as ready for review June 29, 2026 13:02
@draedful draedful requested a review from Antamansid as a code owner June 29, 2026 13:02

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The rapid-stream sticky logic relies on hardcoded rule ids (e.g. comparing to "I5:last-intent" and tests checking rule prefixes), which could become brittle as rules evolve — consider centralizing rule identifiers in constants or an enum to avoid string coupling.
  • In resolveExplicitMouseIntent, markMouseWheelBurst is called for all branches including the fallback I4:input-device-mouse without classic mouse signals; review whether the burst window should be limited to truly mouse-like steps to avoid unintended smoothing behavior in explicit mouse mode.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The rapid-stream sticky logic relies on hardcoded rule ids (e.g. comparing to "I5:last-intent" and tests checking rule prefixes), which could become brittle as rules evolve — consider centralizing rule identifiers in constants or an enum to avoid string coupling.
- In `resolveExplicitMouseIntent`, `markMouseWheelBurst` is called for all branches including the fallback `I4:input-device-mouse` without classic mouse signals; review whether the burst window should be limited to truly mouse-like steps to avoid unintended smoothing behavior in explicit mouse mode.

## Individual Comments

### Comment 1
<location path="src/utils/functions/wheelIntent.ts" line_range="503" />
<code_context>
+ * and Mac Chrome/YaBrowser heuristics are ambiguous.
  */
-export function createWheelIntentResolver(): TResolveWheelIntent {
+export function createWheelIntentResolver(options: TCreateWheelIntentResolverOptions = {}): TResolveWheelIntent {
+  const inputDevice = options.inputDevice ?? "auto";
   let lastIntent: EWheelIntent = EWheelIntent.Zoom;
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing a cohesive TWheelStreamState object and passing it through helpers to centralize stream-related state and simplify resolver branching and helper signatures.

The added input-device and sticky-stream behavior is valuable, but the resolver’s branching and helper signatures have grown enough that a small refactor would reduce cognitive load without changing behavior.

You can keep all functionality while:

1. **Centralizing stream-related state into a small object**
2. **Passing that object into helpers (`resolveExplicitMouseIntent`, `applyRapidStreamStickyIntent`, `emitDebugEntry`) instead of many positional arguments**

This keeps the main decision tree linear and reduces coupling/parameter surface.

### 1. Introduce a `TWheelStreamState` object

```ts
type TWheelStreamState = {
  now: number;
  timeSinceLastWheel: number;
  isRapidStream: boolean;
  inMouseWheelBurst: boolean;
  mouseWheelBurstRemainingMs: number | null;
  lastIntentBefore: EWheelIntent;
  lastRuleBefore: string;
};
```

Build it once inside the resolver:

```ts
return (event: WheelEvent, mouseWheelBehavior: TMouseWheelBehavior): EWheelIntent => {
  const now = performance.now();
  const timeSince = lastTimestamp !== null ? now - lastTimestamp : Number.POSITIVE_INFINITY;
  lastTimestamp = now;

  const isRapidStream = timeSince < RAPID_STREAM_MS;
  const inMouseWheelBurst = isInMouseWheelBurst(now);
  const mouseWheelBurstRemainingMs =
    mouseWheelBurstUntil !== null ? Math.max(0, mouseWheelBurstUntil - now) : null;

  const ctx = createWheelContext(event);
  const signals = buildWheelSignals(ctx);

  const streamState: TWheelStreamState = {
    now,
    timeSinceLastWheel: timeSince,
    isRapidStream,
    inMouseWheelBurst,
    mouseWheelBurstRemainingMs,
    lastIntentBefore: lastIntent,
    lastRuleBefore: lastRule,
  };

  let intent: EWheelIntent;
  let rule: string;

  // ... main decision tree ...
};
```

### 2. Use `streamState` in helpers instead of long parameter lists

`resolveExplicitMouseIntent` becomes simpler and less coupled:

```ts
const resolveExplicitMouseIntent = (
  ctx: TWheelContext,
  signals: TWheelIntentDebugEntry["signals"],
  mouseWheelBehavior: TMouseWheelBehavior,
  stream: TWheelStreamState
): { intent: EWheelIntent; rule: string } => {
  const { isRapidStream, inMouseWheelBurst, now } = stream;

  if (signals.isDominantAxisLargeWheel || signals.isClassicMouseWheelStep || signals.hasLegacyMouseWheelDelta) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: signals.isClassicMouseWheelStep ? "I4:mouse-wheel-step" : "I4:large-step",
    };
  }
  if (isSlowFractionalMouseWheelStep(ctx, isRapidStream, inMouseWheelBurst)) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: "I4:fractional-mouse",
    };
  }
  if (inMouseWheelBurst && signals.isVerticalOnly && isTrackpadLikeRapidSmall(ctx, isRapidStream)) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: "I4-burst:smoothing",
    };
  }
  markMouseWheelBurst(now);
  return {
    intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
    rule: "I4:input-device-mouse",
  };
};
```

`applyRapidStreamStickyIntent` can use the same `streamState`, avoiding extra parameters and making the contract clearer:

```ts
function applyRapidStreamStickyIntent(
  intent: EWheelIntent,
  rule: string,
  signals: TWheelIntentDebugEntry["signals"],
  stream: TWheelStreamState
): { intent: EWheelIntent; rule: string } {
  const { isRapidStream, lastIntentBefore, lastRuleBefore } = stream;

  if (
    !isRapidStream ||
    signals.isPinchZoom ||
    signals.isDiagonalScroll ||
    signals.isPredominantHorizontalScroll ||
    intent === lastIntentBefore ||
    lastRuleBefore === "I5:last-intent"
  ) {
    return { intent, rule };
  }
  return { intent: lastIntentBefore, rule: "I5:sticky-stream" };
}
```

Call sites stay linear:

```ts
if (inputDevice === "mouse") {
  ({ intent, rule } = resolveExplicitMouseIntent(ctx, signals, mouseWheelBehavior, streamState));
} else if (isIntegerPixelTrackpadScroll(ctx, streamState.isRapidStream)) {
  // ...
}

// After main rules:
({ intent, rule } = applyRapidStreamStickyIntent(intent, rule, signals, streamState));

lastIntent = intent;
lastRule = rule;
```

### 3. Use `streamState` and `inputDevice` in debug plumbing

`emitDebugEntry` can accept a single `stream` object plus `inputDevice`, keeping the boundary small:

```ts
function emitDebugEntry(
  ctx: TWheelContext,
  mouseWheelBehavior: TMouseWheelBehavior,
  inputDevice: TWheelInputDevice,
  stream: TWheelStreamState,
  signals: TWheelIntentDebugEntry["signals"],
  rule: string,
  result: EWheelIntent
): void {
  const debugLogger = getWheelIntentDebugLogger();
  if (debugLogger === null) return;

  const { event, normX, normY } = ctx;
  const {
    timeSinceLastWheel,
    isRapidStream,
    inMouseWheelBurst,
    mouseWheelBurstRemainingMs,
    lastIntentBefore,
  } = stream;

  debugLogger({
    mouseWheelBehavior,
    inputDevice,
    input: {
      deltaX: event.deltaX,
      deltaY: event.deltaY,
      deltaMode: event.deltaMode,
      deltaModeLabel: deltaModeLabel(event.deltaMode),
      ctrlKey: event.ctrlKey,
      metaKey: event.metaKey,
      shiftKey: event.shiftKey,
      altKey: event.altKey,
    },
    ctx: {
      normX,
      normY,
      timeSinceLastWheel,
      isRapidStream,
      isInMouseWheelBurst: inMouseWheelBurst,
      mouseWheelBurstRemainingMs,
      lastIntentBefore,
      signals,
    },
    rule,
    result,
  });
}
```

This keeps all existing functionality (explicit `inputDevice`, rapid-stream sticky behavior, burst tracking, Mac Chrome wheelDelta heuristics) while making the resolver easier to follow: stream-related state is built once, passed as a cohesive unit, and both helpers and debug output depend on it rather than on wide positional argument lists.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/utils/functions/wheelIntent.ts Outdated
* and Mac Chrome/YaBrowser heuristics are ambiguous.
*/
export function createWheelIntentResolver(): TResolveWheelIntent {
export function createWheelIntentResolver(options: TCreateWheelIntentResolverOptions = {}): TResolveWheelIntent {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider introducing a cohesive TWheelStreamState object and passing it through helpers to centralize stream-related state and simplify resolver branching and helper signatures.

The added input-device and sticky-stream behavior is valuable, but the resolver’s branching and helper signatures have grown enough that a small refactor would reduce cognitive load without changing behavior.

You can keep all functionality while:

  1. Centralizing stream-related state into a small object
  2. Passing that object into helpers (resolveExplicitMouseIntent, applyRapidStreamStickyIntent, emitDebugEntry) instead of many positional arguments

This keeps the main decision tree linear and reduces coupling/parameter surface.

1. Introduce a TWheelStreamState object

type TWheelStreamState = {
  now: number;
  timeSinceLastWheel: number;
  isRapidStream: boolean;
  inMouseWheelBurst: boolean;
  mouseWheelBurstRemainingMs: number | null;
  lastIntentBefore: EWheelIntent;
  lastRuleBefore: string;
};

Build it once inside the resolver:

return (event: WheelEvent, mouseWheelBehavior: TMouseWheelBehavior): EWheelIntent => {
  const now = performance.now();
  const timeSince = lastTimestamp !== null ? now - lastTimestamp : Number.POSITIVE_INFINITY;
  lastTimestamp = now;

  const isRapidStream = timeSince < RAPID_STREAM_MS;
  const inMouseWheelBurst = isInMouseWheelBurst(now);
  const mouseWheelBurstRemainingMs =
    mouseWheelBurstUntil !== null ? Math.max(0, mouseWheelBurstUntil - now) : null;

  const ctx = createWheelContext(event);
  const signals = buildWheelSignals(ctx);

  const streamState: TWheelStreamState = {
    now,
    timeSinceLastWheel: timeSince,
    isRapidStream,
    inMouseWheelBurst,
    mouseWheelBurstRemainingMs,
    lastIntentBefore: lastIntent,
    lastRuleBefore: lastRule,
  };

  let intent: EWheelIntent;
  let rule: string;

  // ... main decision tree ...
};

2. Use streamState in helpers instead of long parameter lists

resolveExplicitMouseIntent becomes simpler and less coupled:

const resolveExplicitMouseIntent = (
  ctx: TWheelContext,
  signals: TWheelIntentDebugEntry["signals"],
  mouseWheelBehavior: TMouseWheelBehavior,
  stream: TWheelStreamState
): { intent: EWheelIntent; rule: string } => {
  const { isRapidStream, inMouseWheelBurst, now } = stream;

  if (signals.isDominantAxisLargeWheel || signals.isClassicMouseWheelStep || signals.hasLegacyMouseWheelDelta) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: signals.isClassicMouseWheelStep ? "I4:mouse-wheel-step" : "I4:large-step",
    };
  }
  if (isSlowFractionalMouseWheelStep(ctx, isRapidStream, inMouseWheelBurst)) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: "I4:fractional-mouse",
    };
  }
  if (inMouseWheelBurst && signals.isVerticalOnly && isTrackpadLikeRapidSmall(ctx, isRapidStream)) {
    markMouseWheelBurst(now);
    return {
      intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
      rule: "I4-burst:smoothing",
    };
  }
  markMouseWheelBurst(now);
  return {
    intent: intentFromMouseWheelBehavior(mouseWheelBehavior),
    rule: "I4:input-device-mouse",
  };
};

applyRapidStreamStickyIntent can use the same streamState, avoiding extra parameters and making the contract clearer:

function applyRapidStreamStickyIntent(
  intent: EWheelIntent,
  rule: string,
  signals: TWheelIntentDebugEntry["signals"],
  stream: TWheelStreamState
): { intent: EWheelIntent; rule: string } {
  const { isRapidStream, lastIntentBefore, lastRuleBefore } = stream;

  if (
    !isRapidStream ||
    signals.isPinchZoom ||
    signals.isDiagonalScroll ||
    signals.isPredominantHorizontalScroll ||
    intent === lastIntentBefore ||
    lastRuleBefore === "I5:last-intent"
  ) {
    return { intent, rule };
  }
  return { intent: lastIntentBefore, rule: "I5:sticky-stream" };
}

Call sites stay linear:

if (inputDevice === "mouse") {
  ({ intent, rule } = resolveExplicitMouseIntent(ctx, signals, mouseWheelBehavior, streamState));
} else if (isIntegerPixelTrackpadScroll(ctx, streamState.isRapidStream)) {
  // ...
}

// After main rules:
({ intent, rule } = applyRapidStreamStickyIntent(intent, rule, signals, streamState));

lastIntent = intent;
lastRule = rule;

3. Use streamState and inputDevice in debug plumbing

emitDebugEntry can accept a single stream object plus inputDevice, keeping the boundary small:

function emitDebugEntry(
  ctx: TWheelContext,
  mouseWheelBehavior: TMouseWheelBehavior,
  inputDevice: TWheelInputDevice,
  stream: TWheelStreamState,
  signals: TWheelIntentDebugEntry["signals"],
  rule: string,
  result: EWheelIntent
): void {
  const debugLogger = getWheelIntentDebugLogger();
  if (debugLogger === null) return;

  const { event, normX, normY } = ctx;
  const {
    timeSinceLastWheel,
    isRapidStream,
    inMouseWheelBurst,
    mouseWheelBurstRemainingMs,
    lastIntentBefore,
  } = stream;

  debugLogger({
    mouseWheelBehavior,
    inputDevice,
    input: {
      deltaX: event.deltaX,
      deltaY: event.deltaY,
      deltaMode: event.deltaMode,
      deltaModeLabel: deltaModeLabel(event.deltaMode),
      ctrlKey: event.ctrlKey,
      metaKey: event.metaKey,
      shiftKey: event.shiftKey,
      altKey: event.altKey,
    },
    ctx: {
      normX,
      normY,
      timeSinceLastWheel,
      isRapidStream,
      isInMouseWheelBurst: inMouseWheelBurst,
      mouseWheelBurstRemainingMs,
      lastIntentBefore,
      signals,
    },
    rule,
    result,
  });
}

This keeps all existing functionality (explicit inputDevice, rapid-stream sticky behavior, burst tracking, Mac Chrome wheelDelta heuristics) while making the resolver easier to follow: stream-related state is built once, passed as a cohesive unit, and both helpers and debug output depend on it rather than on wide positional argument lists.

draedful and others added 2 commits June 29, 2026 16:52
Replace string literals and startsWith checks with WHEEL_INTENT_RULE and Set-based helpers for faster, type-safe rule comparisons.

Co-authored-by: Cursor <cursoragent@cursor.com>
…stants

Move WHEEL_INPUT_DEVICE to camera constants and bundle mouseWheelBehavior with wheelInputDevice in TResolveWheelIntentOptions so the default resolver no longer needs recreation.

Co-authored-by: Cursor <cursoragent@cursor.com>
@draedful draedful merged commit 1d39a7e into main Jun 29, 2026
6 checks passed
@draedful draedful deleted the cursor/fix-mac-chrome-trackpad-wheel-intent branch June 29, 2026 14:33
@gravity-ui gravity-ui Bot mentioned this pull request Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants