Skip to content

fix: removed client-side sensitivity double-application#54

Open
Rozerxshashank wants to merge 3 commits intoAOSSIE-Org:mainfrom
Rozerxshashank:fix/sensitivity-bug
Open

fix: removed client-side sensitivity double-application#54
Rozerxshashank wants to merge 3 commits intoAOSSIE-Org:mainfrom
Rozerxshashank:fix/sensitivity-bug

Conversation

@Rozerxshashank
Copy link

@Rozerxshashank Rozerxshashank commented Feb 10, 2026

Fixes: #52

This PR fixes a bug where mouse sensitivity was being applied twice for cursor movement (client hardcoded + server config), while being ignored completely for scroll and zoom events.

Changes:

  1. Removed the sensitivity parameter and its hardcoded 1.5 default. The client now sends raw touch deltas.

  2. Updated InputHandler.ts to apply CONFIG.MOUSE_SENSITIVITY to Scroll and Zoom events. Previously, it only applied sensitivity to mouse movement.

How Has This Been Tested?

Manual Verification: Verified that removing the client-side multiplier correctly delegates sensitivity logic to the server.

Build Check: Ran npm run build to ensure no type errors or build regressions.

Checklist
My code follows the style guidelines of this project
I have performed a self-review of my own code
I have commented my code, particularly in hard-to-understand areas
My changes generate no new warnings.

Summary by CodeRabbit

  • Refactor

    • Simplified trackpad gesture API: per-device sensitivity, inversion and axis thresholds removed in favor of a single global input approach.
  • Bug Fix

    • Scroll and zoom deltas now use consistent scaling, rounding and sign handling to avoid tiny/no-op zooms and erratic scrolls.
  • Improvement

    • Stabilized event handlers, reduced re-renders, cleaner modifier/input handling, and configurable click/focus timing constants.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Client-side trackpad no longer applies sensitivity/inversion/axis-threshold; it sends raw gesture deltas. Server InputHandler now reads CONFIG (sensitivity/invert), applies scaling/sign, rounds values, and guards zero amounts before performing scroll/zoom/move actions.

Changes

Cohort / File(s) Summary
Client: gesture hook
src/hooks/useTrackpadGesture.ts
Removed sensitivity, invertScroll, and axisThreshold parameters; sends raw sumX, sumY, and delta without client-side scaling or per-axis thresholding.
Client: trackpad UI / handlers
src/routes/trackpad.tsx
Replaced hard-coded timeouts with CLICK_RELEASE_DELAY_MS / INPUT_REFOCUS_DELAY_MS; stabilized handlers with useCallback; removed local sensitivity/invert state and updated useTrackpadGesture usage; refactored modifier and text-send logic.
Server: input handling
src/server/InputHandler.ts
Now reads CONFIG.MOUSE_SENSITIVITY and CONFIG.MOUSE_INVERT, applies invert/sensitivity multipliers, scales and rounds deltas for move/scroll/zoom, and skips actions when rounded amount is zero.
Project metadata
manifest_file, package.json
Minor manifest/package edits to accompany code changes (lines changed: +10/-30).

Sequence Diagram(s)

sequenceDiagram
actor Client
participant Server
participant Config
rect rgba(100,150,240,0.5)
Client->>Client: capture gesture (sumX, sumY, delta)
end
Client->>Server: send raw gesture message (sumX, sumY, delta)
Server->>Config: read CONFIG (MOUSE_SENSITIVITY, MOUSE_INVERT)
Config-->>Server: sensitivity, invert flag
Server->>Server: compute invertMultiplier, scaled = raw * sensitivity * invertMultiplier
Server->>Server: round/scalarize values
alt amount != 0
    Server->>Server: perform move / scroll / zoom action
end
Server-->>Client: (optional) ack/state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I counted taps and twitches, neat and small,
Sent naked numbers, no scaling at all.
The server now measures, flips, and rounds with care—
One place for speed, no double-hop to spare. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: removing client-side sensitivity double-application, which is the core issue resolved.
Linked Issues check ✅ Passed Changes comprehensively address both problems in issue #52: removing hardcoded 1.5x multiplier from client-side gesture hook and applying CONFIG.MOUSE_SENSITIVITY to scroll/zoom in server handler.
Out of Scope Changes check ✅ Passed All changes align with fixing the sensitivity double-application bug. The use of useCallback and refactoring in trackpad.tsx support the core fix without introducing unrelated functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/server/InputHandler.ts`:
- Around line 47-52: The switch cases for 'scroll' (where invertMultiplier and
scrollSensitivity are declared and mouse.scrollDown/mouse.scrollRight are
called) and the 'zoom' case must be wrapped in their own block braces to prevent
const-scope leakage across cases; edit the case 'scroll' and case 'zoom' bodies
to enclose their existing statements in { ... } so invertMultiplier,
scrollSensitivity and any local vars in the 'zoom' case are block-scoped to that
case.
- Around line 50-51: The scroll amounts passed to mouse.scrollDown and
mouse.scrollRight (in InputHandler.ts) can be non-integers because
scrollSensitivity may be a float, causing native truncation; wrap the computed
values (msg.dy * invertMultiplier * scrollSensitivity and msg.dx * -1 *
invertMultiplier * scrollSensitivity) with Math.round() before awaiting
mouse.scrollDown and mouse.scrollRight so the nut-js functions receive integer
ticks (use msg.dy/msg.dx checks as-is).
🧹 Nitpick comments (1)
src/server/InputHandler.ts (1)

27-27: CONFIG.MOUSE_SENSITIVITY ?? 1.0 is repeated three times; the fallback is already in config.tsx.

CONFIG.MOUSE_SENSITIVITY already defaults to 1.0 via the nullish coalescing in src/config.tsx (line 20). The ?? 1.0 here is redundant. Consider reading it once at the top of handleMessage (or as a class field) to keep it DRY:

  async handleMessage(msg: InputMessage) {
+     const sensitivity = CONFIG.MOUSE_SENSITIVITY;
      switch (msg.type) {

Then reuse sensitivity in the move, scroll, and zoom branches.

Also applies to: 49-49, 57-57

@Rozerxshashank
Copy link
Author

Hey @imxade
When I removed that multiplier to fix the cursor sensitivity bug, scroll and zoom events became unscaled. I updated InputHandler.ts to apply the server config to these events as well, ensuring consistent behavior across all gesture types. This makes the fix more robust and prevents a regression in scroll/zoom usability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/trackpad.tsx (1)

40-66: ⚠️ Potential issue | 🔴 Critical

Stale closures: handleKeyDown, handleInput, and handleCompositionEnd will read stale modifier state.

handleKeyDown (line 40) captures modifier and calls handleModifier, but its dependency array is [send]. When the user toggles modifier state, this callback won't see the update — key presses will be routed incorrectly (e.g., keys sent as normal input when modifier is "Active", or vice versa). The same issue affects handleInput (line 104) and handleCompositionEnd (line 121).

The root fix: wrap handleModifier in useCallback (with its own correct deps), then include both modifier and handleModifier in the dependency arrays of all three hooks.

Proposed fix (sketch)
-const handleModifier = (key: string) => {
+const handleModifier = useCallback((key: string) => {
     if (modifier === "Hold") {
         const comboKeys = [...buffer, key];
         sendCombo(comboKeys);
         return;
     } else if (modifier === "Active") {
         setBuffer(prev => [...prev, key]);
         return;
     }
-};
+}, [modifier, buffer, sendCombo]);

-    const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
+    const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
         // ... same body ...
-    }, [send]);
+    }, [send, modifier, handleModifier]);

-    const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
+    const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
         // ... same body ...
-    }, [sendText]);
+    }, [sendText, modifier, handleModifier]);

-    const handleCompositionEnd = useCallback((e: React.CompositionEvent<HTMLInputElement>) => {
+    const handleCompositionEnd = useCallback((e: React.CompositionEvent<HTMLInputElement>) => {
         // ... same body ...
-    }, [sendText]);
+    }, [sendText, modifier, handleModifier]);
🤖 Fix all issues with AI agents
In `@src/routes/trackpad.tsx`:
- Around line 185-187: The onBlur handler currently always refocuses
hiddenInputRef after INPUT_REFOCUS_DELAY_MS which can steal focus from other
controls; change the setTimeout callback to check document.activeElement and
only call hiddenInputRef.current?.focus() when the new activeElement is the
document.body or not inside the trackpad container (use a trackpad container ref
such as trackpadContainerRef.current) — ensure you guard against null refs and
only refocus when the activeElement is the body or outside the container (or
equals the container) to avoid trapping focus.
- Around line 27-28: The inline comment above the useTrackpadGesture call is
stale: it still reads "Pass sensitivity and invertScroll to the gesture hook"
even though the call now is const { isTracking, handlers } =
useTrackpadGesture(send, scrollMode); Update or remove that comment to reflect
the current usage (e.g., "Pass send and scrollMode to the gesture hook" or
remove the line entirely), locating the string near the useTrackpadGesture
invocation to keep docs accurate.

In `@src/server/InputHandler.ts`:
- Line 27: The file references CONFIG (e.g., the sensitivity assignment in
InputHandler.ts and handlers for move/scroll/zoom) but CONFIG isn't imported and
the existing config export doesn't include MOUSE_SENSITIVITY/MOUSE_INVERT; add
or import the correct configuration values and provide safe defaults: either (A)
update the config module to export MOUSE_SENSITIVITY and MOUSE_INVERT and import
CONFIG (or the named exports) into InputHandler, or (B) replace
CONFIG.MOUSE_SENSITIVITY/CONFIG.MOUSE_INVERT uses with locally read values that
fallback to 1.0 and false respectively (e.g., const sensitivity =
process.env.MOUSE_SENSITIVITY ? Number(...) : 1.0). Ensure all occurrences in
move/scroll/zoom handlers reference the defined variables and include proper
type/coercion to avoid runtime ReferenceError and NaN.
- Around line 29-31: The mouse move handler in InputHandler (the call to
mouse.setPosition with new Point) currently passes floating-point coordinates,
causing inconsistency with scroll/zoom which round values; update the code that
computes the new Point for mouse.setPosition to wrap both X and Y in Math.round
(i.e., Math.round(currentPos.x + msg.dx * sensitivity) and
Math.round(currentPos.y + msg.dy * sensitivity)) so the move path matches
scroll/zoom rounding behavior and avoid subtle coordinate differences.
🧹 Nitpick comments (2)
src/server/InputHandler.ts (1)

73-74: break is outside the block scope for the zoom case.

The break on line 74 sits after the closing } of the block on line 73. While syntactically valid, this is inconsistent with the scroll case (where break is inside the block) and easy to misread. Move it inside the block for consistency.

Proposed fix
             case 'zoom': {
                 if (msg.delta !== undefined && msg.delta !== 0) {
                     ...
                 }
-            }
                 break;
+                break;
+            }
src/routes/trackpad.tsx (1)

68-86: handleModifierState is not memoized — will create a new reference every render, causing ControlBar to re-render unnecessarily.

Since it's passed to ControlBar via onModifierToggle, wrapping it in useCallback would be consistent with the other handlers in this file.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/trackpad.tsx (1)

39-65: ⚠️ Potential issue | 🔴 Critical

Stale closures: handleKeyDown, handleInput, and handleCompositionEnd will read outdated modifier state.

handleKeyDown captures modifier and calls handleModifier, but the dependency array only lists [send]. When modifier changes (e.g., user activates "Hold" mode), subsequent keystrokes will still see the old modifier value. The same problem affects handleInput (Line 103) and handleCompositionEnd (Line 120). This is confirmed by the Biome linter.

The root fix is to either:

  1. Add modifier and a memoized handleModifier to each dependency array, or
  2. Convert modifier reads to use a ref so callbacks don't need to re-create.

Option 1 (minimal fix) — wrap handleModifier in useCallback and fix dep arrays:

Proposed fix
-const handleModifier = (key: string) => {
+const handleModifier = useCallback((key: string) => {
     if (modifier === "Hold") {
         const comboKeys = [...buffer, key];
         sendCombo(comboKeys);
         return;
     } else if (modifier === "Active") {
         setBuffer(prev => [...prev, key]);
         return;
     }
-};
+}, [modifier, buffer, sendCombo]);

 ...

     const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
         ...
-    }, [send]);
+    }, [send, modifier, handleModifier]);

     const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
         ...
-    }, [sendText]);
+    }, [sendText, modifier, handleModifier]);

     const handleCompositionEnd = useCallback((e: React.CompositionEvent<HTMLInputElement>) => {
         ...
-    }, [sendText]);
+    }, [sendText, modifier, handleModifier]);
🧹 Nitpick comments (2)
src/routes/trackpad.tsx (2)

67-96: handleModifierState and handleModifier are plain functions — inconsistent with the useCallback pattern used elsewhere.

These two functions close over modifier, buffer, sendCombo, and state setters but are not memoized. They are passed to child components (e.g., onModifierToggle={handleModifierState}) and called from memoized callbacks, which undermines the benefit of useCallback on the callers and causes referential instability for child props.

Wrapping both in useCallback with correct deps would complete the memoization story and also make the stale-closure fix above cleaner.


167-174: Inline arrow in sendKey prop defeats memoization of ExtraKeys.

A new closure is created every render. If ExtraKeys is wrapped in React.memo, this negates it. Consider extracting to a useCallback.

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.

[Bug]: Sensitivity slider value is not wired to the trackpad, setting is ignored and double-applied

1 participant