Skip to content

feat: remove stale gestures when diff#2297

Merged
Yradex merged 2 commits intolynx-family:mainfrom
f0rdream:gesture_stale_removal
Apr 13, 2026
Merged

feat: remove stale gestures when diff#2297
Yradex merged 2 commits intolynx-family:mainfrom
f0rdream:gesture_stale_removal

Conversation

@f0rdream
Copy link
Copy Markdown
Collaborator

@f0rdream f0rdream commented Mar 3, 2026

Summary by CodeRabbit

  • New Features

    • Exposed a gesture-detector removal API and added an interactive Gesture Lifecycle example app with config and build scripts.
  • Bug Fixes

    • Remove stale gesture detectors when gestures are removed, replaced, or reordered to prevent lingering state and incorrect handlers.
  • Tests

    • Expanded unit and integration tests covering gesture lifecycle, deduplication, diffs, and detector wiring.
  • Style

    • Added themed stylesheet for the gesture example UI.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@f0rdream f0rdream requested review from HuJean, Yradex and hzy as code owners March 3, 2026 14:00
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 3, 2026

🦋 Changeset detected

Latest commit: b34a34e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@lynx-js/testing-environment Patch
@lynx-js/react Patch
@lynx-js/react-umd Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 3, 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

Adds a PAPI binding __RemoveGestureDetector, refactors gesture runtime to track multiple detectors per element and remove stale detectors, updates typings and element PAPI, expands tests, and adds a gesture example app.

Changes

Cohort / File(s) Summary
Changesets
\.changeset/all-crabs-smell.md, \.changeset/fair-horses-worry.md
Add changeset entries documenting patch releases and the gesture-detector changes.
Runtime gesture core
packages/react/runtime/src/gesture/processGesture.ts
Refactor gesture processing: flatten/deduplicate base gestures, pre-collect old bases, remove stale detectors, fast-path single-base updates, and call __RemoveGestureDetector where appropriate.
Element PAPI instrumentation & types
packages/react/runtime/src/alog/elementPAPICall.ts, packages/react/runtime/types/types.d.ts
Instrument and declare __RemoveGestureDetector in PAPI wrapping and global types.
Test runtime helpers & snapshots
packages/react/runtime/__test__/utils/nativeMethod.ts, packages/react/runtime/__test__/snapshot/gesture.test.jsx, packages/react/runtime/__test__/gesture/processGesture.test.ts
Introduce per-element gestureDetectorMap, add __RemoveGestureDetector, __GetGestureDetector, __GetGestureDetectorIds, extend/replace snapshots with explicit set/remove assertions and new gesture diff/reorder tests.
Testing-environment Element PAPI
packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts, packages/testing-library/testing-environment/etc/testing-environment.api.md
Expose and implement __RemoveGestureDetector(e, id) in the testing environment API and docs.
Examples: gesture demo
examples/gesture/lynx.config.js, examples/gesture/package.json, examples/gesture/src/App.tsx, examples/gesture/src/index.tsx, examples/gesture/src/App.css, examples/gesture/src/rspeedy-env.d.ts, examples/gesture/tsconfig.json
Add a new gesture playground example app, build/dev config, TS setup, styles, and entrypoint demonstrating set/diff/remove modes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • hzy
  • Yradex
  • colinaaa

Poem

🐇 I hop where callbacks weave and play,
I tidy detectors swept away.
When gestures shift, I nudge and clear,
Remove the stale, keep wiring near.
Hoppity—small rabbit, proud and dear. 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: remove stale gestures when diff' directly describes the core change: removing stale gesture detectors during diff operations when composed gestures are updated or reduced.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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.

🧹 Nitpick comments (1)
packages/react/runtime/__test__/snapshot/gesture.test.jsx (1)

360-360: Consider extracting repeated text element lookup helper.

The traversal __root.__element_root.children[0].children[0] is repeated multiple times and is brittle to tree-shape changes; a local helper would improve maintainability.

♻️ Optional refactor
+const getTextElement = () => __root.__element_root.children[0].children[0];
...
-const textElement = __root.__element_root.children[0].children[0];
+const textElement = getTextElement();

Also applies to: 702-702, 848-848, 1119-1119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` at line 360, The
test repeats the brittle traversal
__root.__element_root.children[0].children[0]; extract a small helper (e.g.,
getTextElement or findTextElement) that returns that node so all occurrences
call that function instead of duplicating the path; update usages at the
locations referencing __root.__element_root.children[0].children[0] to call the
helper, improving readability and making future tree-shape changes easier to
handle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx`:
- Line 360: The test repeats the brittle traversal
__root.__element_root.children[0].children[0]; extract a small helper (e.g.,
getTextElement or findTextElement) that returns that node so all occurrences
call that function instead of duplicating the path; update usages at the
locations referencing __root.__element_root.children[0].children[0] to call the
helper, improving readability and making future tree-shape changes easier to
handle.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cbe5d8 and 05b9dc0.

📒 Files selected for processing (9)
  • .changeset/all-crabs-smell.md
  • .changeset/fair-horses-worry.md
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/utils/nativeMethod.ts
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/types/types.d.ts
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts

@f0rdream f0rdream force-pushed the gesture_stale_removal branch from 05b9dc0 to 5ba138f Compare March 4, 2026 13:44
Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (4)
packages/react/runtime/src/gesture/processGesture.ts (1)

190-207: Verify: Positional fallback for old gesture matching.

The fallback ?? uniqOldBaseGestures[index] on line 197 uses positional matching when gesture IDs don't match. This is passed to getGestureInfo which uses it for onWorkletCtxUpdate callback diffing.

This heuristic works well when gesture order is preserved but IDs regenerate. However, if gesture structure changes significantly (e.g., gestures reordered), incorrect oldGesture references could lead to suboptimal worklet context updates.

Consider whether this is the intended behavior or if passing undefined when ID lookup fails would be safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/src/gesture/processGesture.ts` around lines 190 - 207,
The current positional fallback (uniqOldBaseGestures[index]) when
oldBaseGesturesById.get(baseGesture.id) returns undefined can incorrectly pair
gestures after reordering; update the matching logic so that if the ID lookup
fails we pass undefined into getGestureInfo instead of using the positional
fallback—i.e., replace the expression oldBaseGesturesById.get(baseGesture.id) ??
uniqOldBaseGestures[index] with just oldBaseGesturesById.get(baseGesture.id) (or
explicit ?? undefined) so getGestureInfo (and its onWorkletCtxUpdate diffing)
only compares by actual ID matches; keep the surrounding loop and subsequent
__SetGestureDetector/removeGestureDetector calls unchanged.
packages/react/runtime/__test__/gesture/processGesture.test.ts (1)

91-126: Make “remove before set” ordering explicit in assertions.

These tests validate both calls happened, but not the sequence. Since the behavior contract is specifically stale-removal-first, please add an explicit order assertion so set-before-remove cannot pass.

Also applies to: 199-228

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/gesture/processGesture.test.ts` around lines
91 - 126, The test currently asserts both removeGestureDetector and
setGestureDetector were called but not their sequence; update the test for
processGesture to explicitly assert remove happens before set by checking the
mock invocation order (compare removeGestureDetector.mock.invocationCallOrder[0]
< setGestureDetector.mock.invocationCallOrder[0]) after the existing
expectations; apply the same explicit ordering assertion to the other similar
test block (the one around lines 199-228) to enforce the "remove-before-set"
contract for processGesture.
examples/gesture/src/App.tsx (1)

18-84: Consider extracting shared PanGesture wiring to reduce drift.

panGestureA and panGestureB currently duplicate most lifecycle handler structure. A small helper/custom hook for shared begin/end handling and axis-specific update logic would make future edits safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/gesture/src/App.tsx` around lines 18 - 84, panGestureA and
panGestureB duplicate lifecycle handlers; extract shared wiring into a helper
(e.g., createPanHandlers or useSharedPanGesture) to centralize onBegin/onEnd
logic and accept an axis-specific onUpdate callback. Move shared behavior that
uses startPointMTRef and baseOffsetMTRef (setting startPoint, opacity changes,
computing dx/dy updates to baseOffset, and logging) into that helper, and have
panGestureA/panGestureB call useGesture(PanGesture).onBegin/onUpdate/onEnd with
the shared handlers, passing a small axis-specific updater for panGestureB to
only modify x. This keeps unique parts (axis constrain and transform string)
configurable while reusing start/end behavior and refs.
packages/react/runtime/__test__/snapshot/gesture.test.jsx (1)

850-872: Add explicit sequence checks for remove-then-set behavior.

In update/diff paths, these assertions should also verify removal occurs before installation of the new detector to lock the stale-cleanup guarantee.

Also applies to: 1176-1180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` around lines 850 -
872, Add assertions that the removal spy is invoked before the set spy to
enforce remove-then-set ordering: after the existing checks referencing
spySetGesture, spyRemoveGesture and elementTree.__GetGestureDetectorIds, add an
order assertion using Jest's mock.invocationCallOrder (e.g.,
expect(spyRemoveGesture.mock.invocationCallOrder[0]).toBeLessThan(spySetGesture.mock.invocationCallOrder[0]))
so the test verifies removal happens before installation; apply the same change
at the other location that mirrors these assertions (the block around lines
1176-1180) and reference the same spies (spyRemoveGesture, spySetGesture) when
adding the order check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts`:
- Around line 318-322: The condition in __RemoveGestureDetector incorrectly
checks e.gesture?.[id] (accessing a numeric property) so removal can fail;
update the check to compare the stored gesture object's id (e.g., e.gesture?.id
=== id) to match how __SetGestureDetector stores gesture as { id, type, config,
relationMap }, then delete e.gesture when the ids match; mirror the approach
used in __SetGestureDetector and the test reference where they use
props.gesture?.id.

---

Nitpick comments:
In `@examples/gesture/src/App.tsx`:
- Around line 18-84: panGestureA and panGestureB duplicate lifecycle handlers;
extract shared wiring into a helper (e.g., createPanHandlers or
useSharedPanGesture) to centralize onBegin/onEnd logic and accept an
axis-specific onUpdate callback. Move shared behavior that uses startPointMTRef
and baseOffsetMTRef (setting startPoint, opacity changes, computing dx/dy
updates to baseOffset, and logging) into that helper, and have
panGestureA/panGestureB call useGesture(PanGesture).onBegin/onUpdate/onEnd with
the shared handlers, passing a small axis-specific updater for panGestureB to
only modify x. This keeps unique parts (axis constrain and transform string)
configurable while reusing start/end behavior and refs.

In `@packages/react/runtime/__test__/gesture/processGesture.test.ts`:
- Around line 91-126: The test currently asserts both removeGestureDetector and
setGestureDetector were called but not their sequence; update the test for
processGesture to explicitly assert remove happens before set by checking the
mock invocation order (compare removeGestureDetector.mock.invocationCallOrder[0]
< setGestureDetector.mock.invocationCallOrder[0]) after the existing
expectations; apply the same explicit ordering assertion to the other similar
test block (the one around lines 199-228) to enforce the "remove-before-set"
contract for processGesture.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx`:
- Around line 850-872: Add assertions that the removal spy is invoked before the
set spy to enforce remove-then-set ordering: after the existing checks
referencing spySetGesture, spyRemoveGesture and
elementTree.__GetGestureDetectorIds, add an order assertion using Jest's
mock.invocationCallOrder (e.g.,
expect(spyRemoveGesture.mock.invocationCallOrder[0]).toBeLessThan(spySetGesture.mock.invocationCallOrder[0]))
so the test verifies removal happens before installation; apply the same change
at the other location that mirrors these assertions (the block around lines
1176-1180) and reference the same spies (spyRemoveGesture, spySetGesture) when
adding the order check.

In `@packages/react/runtime/src/gesture/processGesture.ts`:
- Around line 190-207: The current positional fallback
(uniqOldBaseGestures[index]) when oldBaseGesturesById.get(baseGesture.id)
returns undefined can incorrectly pair gestures after reordering; update the
matching logic so that if the ID lookup fails we pass undefined into
getGestureInfo instead of using the positional fallback—i.e., replace the
expression oldBaseGesturesById.get(baseGesture.id) ?? uniqOldBaseGestures[index]
with just oldBaseGesturesById.get(baseGesture.id) (or explicit ?? undefined) so
getGestureInfo (and its onWorkletCtxUpdate diffing) only compares by actual ID
matches; keep the surrounding loop and subsequent
__SetGestureDetector/removeGestureDetector calls unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc2c5867-861d-4996-b94b-1bcfb0cfed8c

📥 Commits

Reviewing files that changed from the base of the PR and between 05b9dc0 and 5ba138f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .changeset/all-crabs-smell.md
  • .changeset/fair-horses-worry.md
  • examples/gesture/lynx.config.js
  • examples/gesture/package.json
  • examples/gesture/src/App.css
  • examples/gesture/src/App.tsx
  • examples/gesture/src/index.tsx
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/tsconfig.json
  • packages/react/runtime/__test__/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/utils/nativeMethod.ts
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/types/types.d.ts
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • .changeset/fair-horses-worry.md
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • .changeset/all-crabs-smell.md

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 94.96855% with 8 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ibrary/testing-environment/src/lynx/ElementPAPI.ts 0.00% 5 Missing ⚠️
...kages/react/runtime/__test__/utils/nativeMethod.ts 89.65% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 4, 2026

Merging this PR will not alter performance

✅ 81 untouched benchmarks
⏩ 21 skipped benchmarks1


Comparing f0rdream:gesture_stale_removal (b34a34e) with main (fd0cc6e)

Open in CodSpeed

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Mar 4, 2026

Web Explorer

#8804 Bundle Size — 748.66KiB (0%).

b34a34e(current) vs fd0cc6e main#8801(baseline)

Bundle metrics  Change 2 changes
                 Current
#8804
     Baseline
#8801
No change  Initial JS 44.27KiB 44.27KiB
No change  Initial CSS 2.16KiB 2.16KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 8 8
No change  Assets 10 10
Change  Modules 148(+1.37%) 146
No change  Duplicate Modules 11 11
Change  Duplicate Code 35.02%(-0.03%) 35.03%
No change  Packages 3 3
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8804
     Baseline
#8801
No change  Other 401.63KiB 401.63KiB
No change  JS 344.87KiB 344.87KiB
No change  CSS 2.16KiB 2.16KiB

Bundle analysis reportBranch f0rdream:gesture_stale_removalProject dashboard


Generated by RelativeCIDocumentationReport issue

@f0rdream f0rdream force-pushed the gesture_stale_removal branch from 5ba138f to b477f9b Compare March 5, 2026 10:06
Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
examples/gesture/src/App.tsx (2)

18-51: Consider wrapping gesture callback registration in useMemo or useEffect.

The gesture callback chain (.onBegin().onUpdate().onEnd()) executes on every render, which may re-register callbacks unnecessarily. For a demo app this is fine, but in production code you'd want to memoize this setup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/gesture/src/App.tsx` around lines 18 - 51, The chained gesture
callbacks registered on panGestureA (returned by useGesture(PanGesture)) are
currently re-attached on every render; move the .onBegin/.onUpdate/.onEnd
registration into a useEffect (or create the gesture once with useMemo) so the
handlers are only registered once or when dependencies change; reference
startPointMTRef and baseOffsetMTRef in the effect dependency list (or use refs
so deps can be stable), and if the gesture API exposes a teardown/unsubscribe,
call it in the effect cleanup to avoid duplicate listeners.

53-84: Shared refs between gestures may cause coordinate drift when switching modes.

Both panGestureA and panGestureB share startPointMTRef and baseOffsetMTRef. When switching from A (X+Y) to B (X-only) and back, the Y offset resets to 0 (line 80), which could cause unexpected position jumps. For a demo this is acceptable, but consider separate refs if independent gesture state is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/gesture/src/App.tsx` around lines 53 - 84, panGestureB and
panGestureA share startPointMTRef and baseOffsetMTRef which causes Y to be reset
to 0 in panGestureB.onEnd; either give panGestureB its own refs (e.g.,
startPointMTRefB, baseOffsetMTRefB) and use them in panGestureB handlers, or
change panGestureB.onEnd to preserve the existing Y by reading
baseOffsetMTRef.current.y and only updating X (set baseOffsetMTRef.current = {
x: baseOffsetMTRef.current.x + dx, y: baseOffsetMTRef.current.y }); update
corresponding onBegin/onUpdate to use the chosen refs consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@examples/gesture/src/App.tsx`:
- Around line 18-51: The chained gesture callbacks registered on panGestureA
(returned by useGesture(PanGesture)) are currently re-attached on every render;
move the .onBegin/.onUpdate/.onEnd registration into a useEffect (or create the
gesture once with useMemo) so the handlers are only registered once or when
dependencies change; reference startPointMTRef and baseOffsetMTRef in the effect
dependency list (or use refs so deps can be stable), and if the gesture API
exposes a teardown/unsubscribe, call it in the effect cleanup to avoid duplicate
listeners.
- Around line 53-84: panGestureB and panGestureA share startPointMTRef and
baseOffsetMTRef which causes Y to be reset to 0 in panGestureB.onEnd; either
give panGestureB its own refs (e.g., startPointMTRefB, baseOffsetMTRefB) and use
them in panGestureB handlers, or change panGestureB.onEnd to preserve the
existing Y by reading baseOffsetMTRef.current.y and only updating X (set
baseOffsetMTRef.current = { x: baseOffsetMTRef.current.x + dx, y:
baseOffsetMTRef.current.y }); update corresponding onBegin/onUpdate to use the
chosen refs consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b66935e9-4876-4d70-b13f-ce4fb643ef3a

📥 Commits

Reviewing files that changed from the base of the PR and between 5ba138f and b477f9b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .changeset/all-crabs-smell.md
  • .changeset/fair-horses-worry.md
  • examples/gesture/lynx.config.js
  • examples/gesture/package.json
  • examples/gesture/src/App.css
  • examples/gesture/src/App.tsx
  • examples/gesture/src/index.tsx
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/tsconfig.json
  • packages/react/runtime/__test__/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/utils/nativeMethod.ts
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/types/types.d.ts
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/react/runtime/test/gesture/processGesture.test.ts
  • .changeset/all-crabs-smell.md
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • examples/gesture/lynx.config.js
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts
  • .changeset/fair-horses-worry.md
  • packages/react/runtime/src/alog/elementPAPICall.ts

Comment thread packages/react/runtime/src/gesture/processGesture.ts Outdated
@f0rdream f0rdream force-pushed the gesture_stale_removal branch from b477f9b to c7d2583 Compare April 13, 2026 06:18
Copy link
Copy Markdown
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react/runtime/__test__/gesture/processGesture.test.ts`:
- Around line 5-7: The test mock replaces onWorkletCtxUpdate with a no-op so
processGesture never triggers lynxWorkletImpl._hydrateCtx; update the mock in
the test to delegate to the real implementation instead of blocking it (e.g.,
use a spy or a wrapper that calls the original onWorkletCtxUpdate) so that
processGesture can exercise lynxWorkletImpl._hydrateCtx and the "consumes old
gestures..." assertion observes the hydrateCtx calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57ffe947-a7d7-4578-acc7-4b1ab7bca41d

📥 Commits

Reviewing files that changed from the base of the PR and between b477f9b and c7d2583.

📒 Files selected for processing (15)
  • .changeset/all-crabs-smell.md
  • .changeset/fair-horses-worry.md
  • examples/gesture/lynx.config.js
  • examples/gesture/package.json
  • examples/gesture/src/App.css
  • examples/gesture/src/App.tsx
  • examples/gesture/src/index.tsx
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/tsconfig.json
  • packages/react/runtime/__test__/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/utils/nativeMethod.ts
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/types/types.d.ts
✅ Files skipped from review due to trivial changes (7)
  • .changeset/fair-horses-worry.md
  • .changeset/all-crabs-smell.md
  • examples/gesture/tsconfig.json
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • examples/gesture/package.json
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/lynx.config.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/gesture/src/index.tsx
  • packages/react/runtime/types/types.d.ts

Comment thread packages/react/runtime/__test__/gesture/processGesture.test.ts Outdated
@f0rdream f0rdream force-pushed the gesture_stale_removal branch from c7d2583 to 83e401c Compare April 13, 2026 06:41
@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 13, 2026

React External

#348 Bundle Size — 591.76KiB (+0.28%).

b34a34e(current) vs fd0cc6e main#345(baseline)

Bundle metrics  Change 1 change
                 Current
#348
     Baseline
#345
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 30.88% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#348
     Baseline
#345
Regression  Other 591.76KiB (+0.28%) 590.13KiB

Bundle analysis reportBranch f0rdream:gesture_stale_removalProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 13, 2026

React Example

#7230 Bundle Size — 236.83KiB (0%).

b34a34e(current) vs fd0cc6e main#7227(baseline)

Bundle metrics  Change 1 change
                 Current
#7230
     Baseline
#7227
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 70 70
Change  Duplicate Code 46.13%(+0.07%) 46.1%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7230
     Baseline
#7227
No change  IMG 145.76KiB 145.76KiB
No change  Other 91.07KiB 91.07KiB

Bundle analysis reportBranch f0rdream:gesture_stale_removalProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 13, 2026

React MTF Example

#363 Bundle Size — 206.12KiB (0%).

b34a34e(current) vs fd0cc6e main#360(baseline)

Bundle metrics  Change 1 change
                 Current
#363
     Baseline
#360
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 67 67
Change  Duplicate Code 45.79%(+0.07%) 45.76%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#363
     Baseline
#360
No change  IMG 111.23KiB 111.23KiB
No change  Other 94.89KiB 94.89KiB

Bundle analysis reportBranch f0rdream:gesture_stale_removalProject dashboard


Generated by RelativeCIDocumentationReport issue

Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
packages/react/runtime/__test__/snapshot/gesture.test.jsx (2)

1117-1117: Minor: prefer idiomatic vitest assertion.

Using .not.toContain() is more readable than .includes().toBe(false).

♻️ Suggested change
-      expect(elementTree.__GetGestureDetectorIds(textElement).includes(1)).toBe(false);
+      expect(elementTree.__GetGestureDetectorIds(textElement)).not.toContain(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` at line 1117,
Replace the non-idiomatic assertion that checks absence via
includes().toBe(false) with Vitest's clearer negated containment matcher: locate
the assertion using elementTree.__GetGestureDetectorIds(textElement) and change
the expectation to use expect(...).not.toContain(1) so the test reads more
idiomatically and improves readability.

15-51: Consider extracting duplicate lynxWorkletImpl mock setup.

The identical lynxWorkletImpl object is defined in both beforeAll (lines 15-30) and beforeEach (lines 36-51). This duplication can lead to maintenance burden if the mock structure changes.

♻️ Proposed refactor to extract shared mock
+const createLynxWorkletImplMock = () => ({
+  _refImpl: {
+    clearFirstScreenWorkletRefMap: vi.fn(),
+  },
+  _runOnBackgroundDelayImpl: {
+    runDelayedBackgroundFunctions: vi.fn(),
+  },
+  _hydrateCtx: vi.fn(),
+  _jsFunctionLifecycleManager: {
+    addRef: vi.fn(),
+  },
+  _eventDelayImpl: {
+    runDelayedWorklet: vi.fn(),
+    clearDelayedWorklets: vi.fn(),
+  },
+});
+
 beforeAll(() => {
   setupPage(__CreatePage('0', 0));
   injectUpdateMainThread();
   replaceCommitHook();
-  globalThis.lynxWorkletImpl = {
-    _refImpl: {
-      clearFirstScreenWorkletRefMap: vi.fn(),
-    },
-    ...
-  };
+  globalThis.lynxWorkletImpl = createLynxWorkletImplMock();
 });

 beforeEach(() => {
   globalEnvManager.resetEnv();
   SystemInfo.lynxSdkVersion = '999.999';
-  globalThis.lynxWorkletImpl = {
-    _refImpl: {
-      clearFirstScreenWorkletRefMap: vi.fn(),
-    },
-    ...
-  };
+  globalThis.lynxWorkletImpl = createLynxWorkletImplMock();
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` around lines 15 -
51, Duplicate mock initialization of globalThis.lynxWorkletImpl in the test file
should be extracted into a single helper to avoid maintenance drift: create a
function (e.g., setupLynxWorkletImplMock) that builds the mock object with the
same nested fns (_refImpl.clearFirstScreenWorkletRefMap,
_runOnBackgroundDelayImpl.runDelayedBackgroundFunctions, _hydrateCtx,
_jsFunctionLifecycleManager.addRef,
_eventDelayImpl.runDelayedWorklet/clearDelayedWorklets) and assign it to
globalThis.lynxWorkletImpl, then call that helper from both beforeAll and
beforeEach (keeping other lines like globalEnvManager.resetEnv() and
SystemInfo.lynxSdkVersion assignment unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx`:
- Line 1117: Replace the non-idiomatic assertion that checks absence via
includes().toBe(false) with Vitest's clearer negated containment matcher: locate
the assertion using elementTree.__GetGestureDetectorIds(textElement) and change
the expectation to use expect(...).not.toContain(1) so the test reads more
idiomatically and improves readability.
- Around line 15-51: Duplicate mock initialization of globalThis.lynxWorkletImpl
in the test file should be extracted into a single helper to avoid maintenance
drift: create a function (e.g., setupLynxWorkletImplMock) that builds the mock
object with the same nested fns (_refImpl.clearFirstScreenWorkletRefMap,
_runOnBackgroundDelayImpl.runDelayedBackgroundFunctions, _hydrateCtx,
_jsFunctionLifecycleManager.addRef,
_eventDelayImpl.runDelayedWorklet/clearDelayedWorklets) and assign it to
globalThis.lynxWorkletImpl, then call that helper from both beforeAll and
beforeEach (keeping other lines like globalEnvManager.resetEnv() and
SystemInfo.lynxSdkVersion assignment unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ed8377b8-5885-4eb5-9c32-20e3aba6033c

📥 Commits

Reviewing files that changed from the base of the PR and between c7d2583 and 83e401c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .changeset/all-crabs-smell.md
  • .changeset/fair-horses-worry.md
  • examples/gesture/lynx.config.js
  • examples/gesture/package.json
  • examples/gesture/src/App.css
  • examples/gesture/src/App.tsx
  • examples/gesture/src/index.tsx
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/tsconfig.json
  • packages/react/runtime/__test__/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/utils/nativeMethod.ts
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/types/types.d.ts
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts
✅ Files skipped from review due to trivial changes (7)
  • .changeset/fair-horses-worry.md
  • .changeset/all-crabs-smell.md
  • examples/gesture/src/rspeedy-env.d.ts
  • examples/gesture/src/index.tsx
  • examples/gesture/lynx.config.js
  • examples/gesture/package.json
  • packages/react/runtime/test/gesture/processGesture.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts
  • packages/react/runtime/types/types.d.ts
  • examples/gesture/tsconfig.json
  • packages/react/runtime/src/alog/elementPAPICall.ts
  • packages/testing-library/testing-environment/etc/testing-environment.api.md
  • packages/react/runtime/test/utils/nativeMethod.ts
  • packages/react/runtime/src/gesture/processGesture.ts

@f0rdream f0rdream requested a review from Yradex April 13, 2026 07:48
@Yradex Yradex merged commit cffd86f into lynx-family:main Apr 13, 2026
50 checks passed
colinaaa pushed a commit that referenced this pull request Apr 19, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @lynx-js/react@0.119.0

### Minor Changes

- Simplify hooks for main-thread runtime, which only can run during the
first screen.
([#2441](#2441))

### Patch Changes

- Remove stale gestures when gestures are removed
([#2297](#2297))

- Trace refactor
([#2466](#2466))

    -   Remove `ReactLynx::renderOpcodes` from the trace
- Use `ReactLynx::transferRoot` to measure the time spent transferring
the root to the background thread

- refactor: set state of suspense to render fallback
([#2450](#2450))

- Support rstest for testing library, you can use rstest with RLTL now:
([#2328](#2328))

    Create a config file `rstest.config.ts` with the following content:

    ```ts
    import { defineConfig } from "@rstest/core";
import { withLynxConfig } from
"@lynx-js/react/testing-library/rstest-config";

    export default defineConfig({
      extends: withLynxConfig(),
    });
    ```

`@lynx-js/react/testing-library/rstest-config` will automatically load
your `lynx.config.ts` and apply the same configuration to rstest, so you
can keep your test environment consistent with your development
environment.

    And then use rstest as usual:

    ```bash
    $ rstest
    ```

    For more usage detail, see <https://rstest.rs/>

- Update preact version
([#2456](#2456))

- Add `nodeIndex` to generated FiberElement creation calls and expose
React transform debug metadata as `uiSourceMapRecords`.
([#2402](#2402))

## @lynx-js/react-rsbuild-plugin@0.16.0

### Minor Changes

- Simplify hooks for main-thread runtime, which only can run during the
first screen.
([#2441](#2441))

### Patch Changes

- Support rstest for testing library using a dedicated testing loader.
([#2328](#2328))

- Fix `environments.lynx.performance.profile` so it overrides the
default profile behavior for the current environment.
([#2468](#2468))

- Add `enableUiSourceMap` option to enable UI source map generation and
debug-metadata asset emission.
([#2402](#2402))

- Updated dependencies
\[[`a9f8d05`](a9f8d05),
[`b1ad1b9`](b1ad1b9),
[`f6184f3`](f6184f3),
[`f6184f3`](f6184f3),
[`a9f8d05`](a9f8d05),
[`f542d9c`](f542d9c)]:
    -   @lynx-js/template-webpack-plugin@0.10.9
    -   @lynx-js/react-webpack-plugin@0.9.1
    -   @lynx-js/react-alias-rsbuild-plugin@0.16.0
    -   @lynx-js/css-extract-webpack-plugin@0.7.0
    -   @lynx-js/react-refresh-webpack-plugin@0.3.5
    -   @lynx-js/use-sync-external-store@1.5.0

## @lynx-js/react-alias-rsbuild-plugin@0.16.0

### Minor Changes

- Simplify hooks for main-thread runtime, which only can run during the
first screen.
([#2441](#2441))

### Patch Changes

- fix(rstest): add global fallback aliases for
`@lynx-js/react/jsx-runtime` and `@lynx-js/react/jsx-dev-runtime`
([#2464](#2464))

`pluginReactAlias` only aliased these entries inside layer-specific
rules (`issuerLayer: BACKGROUND/MAIN_THREAD`). In rstest mode there are
no layers, so JSX transformed by the testing loader—which emits `import
{ jsx } from '@lynx-js/react/jsx-runtime'`—could not be resolved,
causing a `Cannot find module '@lynx-js/react/jsx-runtime'` error. Added
global (non-layer-specific) fallback aliases pointing to the background
jsx-runtime.

## @lynx-js/testing-environment@0.2.0

### Minor Changes

- **BREAKING CHANGE**:
([#2328](#2328))

    Align the public test-environment API around `LynxEnv`.

`LynxTestingEnv` now expects a `{ window }`-shaped environment instead
of relying on a concrete `JSDOM` instance or `global.jsdom`. Callers
that construct `LynxTestingEnv` manually or initialize the environment
through globals should migrate to `new LynxTestingEnv({ window })` or
set `global.lynxEnv`.

This release also adds the `@lynx-js/testing-environment/env/rstest`
entry for running the shared testing-environment suite under rstest.

### Patch Changes

- Add `__RemoveGestureDetector` PAPI binding
([#2297](#2297))

## @lynx-js/rspeedy@0.14.2

### Patch Changes

-   Updated dependencies \[]:
    -   @lynx-js/web-rsbuild-server-middleware@0.20.2

## create-rspeedy@0.14.2

### Patch Changes

- Add Rstest ReactLynx Testing Library template.
([#2328](#2328))

## @lynx-js/external-bundle-rsbuild-plugin@0.1.1

### Patch Changes

- Updated dependencies
\[[`3262ca8`](3262ca8)]:
    -   @lynx-js/externals-loading-webpack-plugin@0.1.1

## @lynx-js/web-core@0.20.2

### Patch Changes

- fix: map clientX and clientY to x and y in touch event detail
([#2458](#2458))

- fix(web-platform): completely detach event listeners and forcefully
free `MainThreadWasmContext` pointer alongside strict FIFO async
component disposal to ensure total memory reclamation without
use-after-free risks
([#2457](#2457))

- refactor: with WeakRef in element APIs and WASM bindings to improve
memory management.
([#2439](#2439))

- fix: preserve CSS variable fallback values when encoding web-core
stylesheets so declarations like `var(--token, rgba(...))` are emitted
with their fallback intact.
([#2460](#2460))

- fix: avoid to do use-after-free for rust instance
([#2461](#2461))

- fix: Change uniqueId to uid in LynxCrossThreadEventTarget
([#2467](#2467))

-   Updated dependencies \[]:
    -   @lynx-js/web-worker-rpc@0.20.2

## @lynx-js/externals-loading-webpack-plugin@0.1.1

### Patch Changes

- fix: deduplicate `loadScript` calls for externals sharing the same
(bundle, section) pair
([#2465](#2465))

When multiple externals had different `libraryName` values but pointed
to the same
bundle URL and section path,
`createLoadExternalSync`/`createLoadExternalAsync` was
called once per external, causing `lynx.loadScript` to execute
redundantly for the
same section. Now only the first external in each `(url, sectionPath)`
group triggers
the load; subsequent externals in the group are assigned the
already-loaded result
    directly.

## @lynx-js/react-webpack-plugin@0.9.1

### Patch Changes

- Support rstest for testing library using a dedicated testing loader.
([#2328](#2328))

- fix(rstest): normalize partial `compat` options in the testing loader
([#2464](#2464))

The testing loader forwards `compat` directly to
`transformReactLynxSync` without normalization. When `compat` is
supplied as a partial object, the required `target` field is absent and
the WASM transform throws `Error: Missing field 'target'`. Added the
same normalization already present in `getCommonOptions` for the
background/main-thread loaders: fills in `target: 'MIXED'` and all other
required fields with sensible defaults.

- Add `enableUiSourceMap` option to enable UI source map generation and
debug-metadata asset emission.
([#2402](#2402))

## @lynx-js/template-webpack-plugin@0.10.9

### Patch Changes

- Introduce `LynxDebugMetadataPlugin` to emit debug-metadata assets.
([#2402](#2402))

- Updated dependencies
\[[`24c4c69`](24c4c69),
[`7332eb4`](7332eb4),
[`fd0cc6e`](fd0cc6e),
[`e5b0f66`](e5b0f66),
[`5aa97d8`](5aa97d8),
[`5c39654`](5c39654)]:
    -   @lynx-js/web-core@0.20.2

## @lynx-js/react-umd@0.119.0



## upgrade-rspeedy@0.14.2



## @lynx-js/web-rsbuild-server-middleware@0.20.2



## @lynx-js/web-worker-rpc@0.20.2

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants