fix(react/runtime): optimize main thread event error message#1838
fix(react/runtime): optimize main thread event error message#1838Yradex merged 1 commit intolynx-family:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 648719e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
📝 WalkthroughWalkthroughIntroduces describeInvalidValue utility and integrates validation into worklet event handling. Updates updateWorkletEvent signature and DEV-time error reporting. Adds tests for invalid value descriptions and worklet event scenarios. Adjusts snapshots accordingly. Includes a changeset updating @lynx-js/react with a patch note. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate 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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (6)
packages/react/runtime/src/debug/describeInvalidValue.ts (1)
5-25: Make descriptions more informative (arrays) and guard very long strings.Current fallback for objects is fine, but two small wins:
- Identify arrays explicitly to reduce confusion.
- Truncate very long strings in messages to avoid noisy logs.
Suggested change:
export function describeInvalidValue(value: unknown): string { + const MAX_STRING_PREVIEW = 120; if (value === null) { return 'null'; } if (value === undefined) { return 'undefined'; } if (typeof value === 'function') { return `function ${value.name || '(anonymous)'}`; } if (typeof value === 'string') { - return `string "${value}"`; + const s = value.length <= MAX_STRING_PREVIEW + ? value + : value.slice(0, MAX_STRING_PREVIEW) + '…'; + return `string "${s}"`; } if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') { return `${typeof value} ${String(value)}`; } if (typeof value === 'symbol') { return `symbol ${String(value)}`; } + if (Array.isArray(value)) { + return `array(length ${value.length})`; + } return `unexpected ${typeof value}`; }packages/react/runtime/__test__/debug/describeInvalidValue.test.ts (1)
29-32: Add coverage for arrays and very long strings.Consider asserting:
- arrays: expect(...).toBe('array(length 3)');
- long strings are truncated with an ellipsis.
Keeps tests aligned with more descriptive messaging if adopted.
packages/react/runtime/__test__/snapshot/workletEvent.test.jsx (2)
424-508: Strengthen expectation to validate value description is surfaced.In addition to checking the attribute name, assert the error message includes the described invalid value (e.g., contains 'function'). This guards the improved error formatting.
Example:
- expect(reportErrorSpy).toHaveBeenCalledWith( - expect.objectContaining({ - message: expect.stringContaining('"main-thread:bindtap"'), - }), - ); + expect(reportErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + message: expect.stringContaining('"main-thread:bindtap"'), + }), + ); + expect(reportErrorSpy.mock.calls[0][0].message).toEqual( + expect.stringContaining('function') + );
510-559: Dev warnings: assert more of the formatted context.Also assert the message includes:
- element tag ()
- snapshot id and name
- the described value, e.g., string "not-a-worklet"
Example:
- expect(getErrorMessage(reportErrorSpy)).toContain('"main-thread:bindtap" on <view>'); + const msg = getErrorMessage(reportErrorSpy); + expect(msg).toContain('"main-thread:bindtap" on <view>'); + expect(msg).toContain('snapshot 42 "TestSnapshot"'); + expect(msg).toContain('string "not-a-worklet"');packages/react/runtime/src/snapshot/workletEvent.ts (2)
16-34: Tune wording for accuracy and consistency.Message says “expected a main-thread function,” while the runtime expects a worklet-like object. Consider “main-thread worklet/handler” to reduce ambiguity.
- const message = `"${eventAttr}" on <${elementTag}> (snapshot ${elementId} "${snapshotName}") expected ` - + 'a main-thread function but received ' + const message = `"${eventAttr}" on <${elementTag}> (snapshot ${elementId} "${snapshotName}") expected ` + + 'a main-thread worklet/handler but received ' + `${describeInvalidValue(value)}. Did you forget to add a "main thread" directive to the handler?`;
47-53: DEV validation: also reject arrays and obviously non‑worklet objects.Today only non-objects are rejected. Arrays or plain objects without worklet markers slip through and may fail later. Recommend expanding the DEV check:
- const rawValue = snapshot.__values![expIndex]; - if (__DEV__ && rawValue !== null && rawValue !== undefined && typeof rawValue !== 'object') { - reportInvalidWorkletValue(snapshot, elementIndex, workletType, eventType, eventName, rawValue); - return; - } + const rawValue = snapshot.__values![expIndex]; + if (__DEV__) { + const isObject = rawValue !== null && rawValue !== undefined && typeof rawValue === 'object'; + const isArray = Array.isArray(rawValue); + const isWorkletLike = + isObject && !isArray && (('_wkltId' in (rawValue as any)) || ('_lepusWorkletHash' in (rawValue as any))); + if (!isObject || isArray || !isWorkletLike) { + reportInvalidWorkletValue(snapshot, elementIndex, workletType, eventType, eventName, rawValue); + return; + } + }This keeps prod behavior unchanged but gives earlier, clearer feedback in DEV.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
.changeset/major-rivers-happen.md(1 hunks)packages/react/runtime/__test__/debug/describeInvalidValue.test.ts(1 hunks)packages/react/runtime/__test__/snapshot/workletEvent.test.jsx(6 hunks)packages/react/runtime/src/debug/describeInvalidValue.ts(1 hunks)packages/react/runtime/src/snapshot/workletEvent.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
For contributions, always generate a changeset and commit the resulting markdown file(s)
Files:
.changeset/major-rivers-happen.md
🧠 Learnings (2)
📚 Learning: 2025-09-12T09:43:04.847Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.847Z
Learning: In the lynx-family/lynx-stack repository, private packages (marked with "private": true in package.json) like lynx-js/react-transform don't require meaningful changeset entries even when their public APIs change, since they are not published externally and only affect internal development.
Applied to files:
.changeset/major-rivers-happen.md
📚 Learning: 2025-09-12T09:43:04.847Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.847Z
Learning: In the lynx-family/lynx-stack repository, empty changeset files (containing only `---\n\n---`) are used for internal changes that modify src/** files but don't require meaningful release notes, such as private package changes or testing-only modifications. This satisfies CI requirements without generating user-facing release notes.
Applied to files:
.changeset/major-rivers-happen.md
🧬 Code graph analysis (3)
packages/react/runtime/__test__/snapshot/workletEvent.test.jsx (3)
packages/react/runtime/__test__/utils/envManager.ts (1)
globalEnvManager(86-86)packages/react/testing-library/src/pure.jsx (2)
render(39-108)waitSchedule(14-20)packages/react/runtime/src/snapshot/workletEvent.ts (1)
updateWorkletEvent(65-65)
packages/react/runtime/__test__/debug/describeInvalidValue.test.ts (1)
packages/react/runtime/src/debug/describeInvalidValue.ts (1)
describeInvalidValue(5-25)
packages/react/runtime/src/snapshot/workletEvent.ts (3)
packages/react/runtime/src/snapshot.ts (1)
SnapshotInstance(278-658)packages/react/runtime/src/debug/describeInvalidValue.ts (1)
describeInvalidValue(5-25)packages/react/worklet-runtime/src/bindings/types.ts (1)
Worklet(41-52)
🔇 Additional comments (3)
.changeset/major-rivers-happen.md (1)
1-6: Changeset present and scoped correctly.Patch entry for "@lynx-js/react" with a concise message looks good and satisfies CI/release requirements. Based on learnings.
packages/react/runtime/src/snapshot/workletEvent.ts (2)
11-14: Attribute formatter reads well and matches expected shape.Handles both 'bindEvent' and 'bind' inputs cleanly.
35-43: No changes needed; allupdateWorkletEventcallers are passing seven arguments in the correct order.
Web Explorer#5568 Bundle Size — 365.44KiB (0%).648719e(current) vs 316da85 main#5554(baseline) Bundle metrics
Bundle size by type
|
| Current #5568 |
Baseline #5554 |
|
|---|---|---|
239.42KiB |
239.42KiB |
|
94.02KiB |
94.02KiB |
|
32KiB |
32KiB |
Bundle analysis report Branch Yradex:fix/mts-error-msg Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#5571 Bundle Size — 237.56KiB (0%).648719e(current) vs 316da85 main#5557(baseline) Bundle metrics
Bundle size by type
|
| Current #5571 |
Baseline #5557 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
91.8KiB |
91.8KiB |
Bundle analysis report Branch Yradex:fix/mts-error-msg Project dashboard
Generated by RelativeCI Documentation Report issue
CodSpeed Performance ReportMerging #1838 will improve performances by 5.58%Comparing 🎉 Hooray!
|
| Benchmark | BASE |
HEAD |
Change | |
|---|---|---|---|---|
| ⚡ | transform 1000 view elements |
43.6 ms | 41.3 ms | +5.58% |
Footnotes
-
3 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. ↩
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.114.1 ### Patch Changes - Add `event.stopPropagation` and `event.stopImmediatePropagation` in MTS, to help with event propagation control ([#1835](#1835)) ```tsx function App() { function handleInnerTap(event: MainThread.TouchEvent) { "main thread"; event.stopPropagation(); // Or stop immediate propagation with // event.stopImmediatePropagation(); } // OuterTap will not be triggered return ( <view main-thread:bindtap={handleOuterTap}> <view main-thread:bindtap={handleInnerTap}> <text>Hello, world</text> </view> </view> ); } ``` Note, if this feature is used in [Lazy Loading Standalone Project](https://lynxjs.org/react/code-splitting.html#lazy-loading-standalone-project), both the Producer and the Consumer should update to latest version of `@lynx-js/react` to make sure the feature is available. - Fix the "ReferenceError: Node is not defined" error. ([#1850](#1850)) This error would happen when upgrading to `@testing-library/jest-dom` [v6.9.0](https://github.com/testing-library/jest-dom/releases/tag/v6.9.0). - fix: optimize main thread event error message ([#1838](#1838)) ## @lynx-js/rspeedy@0.11.5 ### Patch Changes - Bump Rsbuild v1.5.13 with Rspack v1.5.8. ([#1849](#1849)) ## @lynx-js/react-rsbuild-plugin@0.11.1 ### Patch Changes - Updated dependencies \[[`19f823a`](19f823a)]: - @lynx-js/css-extract-webpack-plugin@0.6.4 - @lynx-js/react-alias-rsbuild-plugin@0.11.1 - @lynx-js/use-sync-external-store@1.5.0 - @lynx-js/react-refresh-webpack-plugin@0.3.4 - @lynx-js/react-webpack-plugin@0.7.1 ## @lynx-js/testing-environment@0.1.8 ### Patch Changes - Fix the "ReferenceError: Node is not defined" error. ([#1850](#1850)) This error would happen when upgrading to `@testing-library/jest-dom` [v6.9.0](https://github.com/testing-library/jest-dom/releases/tag/v6.9.0). ## @lynx-js/web-constants@0.17.2 ### Patch Changes - feat: support load bts chunk from remote address ([#1834](#1834)) - re-support chunk splitting - support lynx.requireModule with a json file - support lynx.requireModule, lynx.requireModuleAsync with a remote url - support to add a breakpoint in chrome after reloading the web page - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.17.2 ## @lynx-js/web-core@0.17.2 ### Patch Changes - feat: support load bts chunk from remote address ([#1834](#1834)) - re-support chunk splitting - support lynx.requireModule with a json file - support lynx.requireModule, lynx.requireModuleAsync with a remote url - support to add a breakpoint in chrome after reloading the web page - Updated dependencies \[[`a35a245`](a35a245)]: - @lynx-js/web-worker-runtime@0.17.2 - @lynx-js/web-constants@0.17.2 - @lynx-js/web-mainthread-apis@0.17.2 - @lynx-js/web-worker-rpc@0.17.2 ## @lynx-js/web-mainthread-apis@0.17.2 ### Patch Changes - Updated dependencies \[[`a35a245`](a35a245)]: - @lynx-js/web-constants@0.17.2 - @lynx-js/web-style-transformer@0.17.2 ## @lynx-js/web-worker-runtime@0.17.2 ### Patch Changes - feat: support load bts chunk from remote address ([#1834](#1834)) - re-support chunk splitting - support lynx.requireModule with a json file - support lynx.requireModule, lynx.requireModuleAsync with a remote url - support to add a breakpoint in chrome after reloading the web page - Updated dependencies \[[`a35a245`](a35a245)]: - @lynx-js/web-constants@0.17.2 - @lynx-js/web-mainthread-apis@0.17.2 - @lynx-js/web-worker-rpc@0.17.2 ## @lynx-js/css-extract-webpack-plugin@0.6.4 ### Patch Changes - Avoid generating `.css.hot-update.json` when HMR is disabled. ([#1811](#1811)) ## create-rspeedy@0.11.5 ## @lynx-js/react-alias-rsbuild-plugin@0.11.1 ## upgrade-rspeedy@0.11.5 ## @lynx-js/web-core-server@0.17.2 ## @lynx-js/web-rsbuild-server-middleware@0.17.2 ## @lynx-js/web-style-transformer@0.17.2 ## @lynx-js/web-worker-rpc@0.17.2 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Add a new error msg:
@coderabbitai summary
Checklist