diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md
index bfc9a94e42..9a93c66385 100644
--- a/docs/docs/01-fundamentals/01-getting-started.md
+++ b/docs/docs/01-fundamentals/01-getting-started.md
@@ -84,13 +84,30 @@ pnpm add react-native-executorch react-native-worklets react-native-blob-util
React Native ExecuTorch requires:
- **New Architecture** enabled
-- **React Native 0.81+** or **Expo SDK 54+** with [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/) (**Expo Go is not supported** due to custom C++ native libraries)
+- **React Native 0.83+** or **Expo SDK 55+** with [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/) (**Expo Go is not supported** due to custom C++ native libraries)
+- **`react-native-worklets` 0.10 or newer** (`>=0.10.0 <0.13.0`)
- **iOS 17.0+** / **Android 13+**
For supported React Native versions, see the [Compatibility
table](../05-other/01-compatibility.mdx).
:::
+:::caution Expo SDK 55 and 56
+Both bundle a `react-native-worklets` older than 0.10 — 0.7.4 on SDK 55, 0.8.3
+on SDK 56 — and `npx expo install` will pick that one. Install the version this
+library needs instead:
+
+```bash
+npm install react-native-worklets@^0.10.0
+```
+
+Any other package in your app that uses worklets has to be moved to a release
+built against the same version; two of them asking for different worklets gives
+you two incompatible native runtimes. Our own example apps reconcile them this
+way. **Expo SDK 54 cannot be supported**: it is React Native 0.81, below what
+worklets 0.10 accepts.
+:::
+
### Selecting native libraries
The native binaries — the ExecuTorch hardware backends (XNNPACK, Core ML, MLX,
diff --git a/docs/docs/05-other/01-compatibility.mdx b/docs/docs/05-other/01-compatibility.mdx
index 31c84ddd62..e99fdbb98a 100644
--- a/docs/docs/05-other/01-compatibility.mdx
+++ b/docs/docs/05-other/01-compatibility.mdx
@@ -17,7 +17,7 @@ React Native ExecuTorch supports only the [New Architecture](https://reactnative
| React Native ExecuTorch |
- React Native version |
+ React Native version |
| 0.78 |
@@ -28,6 +28,7 @@ React Native ExecuTorch supports only the [New Architecture](https://reactnative
0.83 |
0.84 |
0.85 |
+ 0.86 |
@@ -41,6 +42,7 @@ React Native ExecuTorch supports only the [New Architecture](https://reactnative
yes |
yes |
yes |
+ untested† |
0.9.x |
@@ -52,18 +54,94 @@ React Native ExecuTorch supports only the [New Architecture](https://reactnative
yes |
yes |
yes |
+ untested† |
0.10.x |
no |
no |
no |
+ no* |
+ no* |
yes |
yes |
yes |
yes |
+
+
+
+
+
+
+
+**\*** `react-native-executorch` 0.10 needs `react-native-worklets`
+`>=0.10.0 <0.13.0`, and worklets 0.10 is the first release to serialize an
+`ArrayBufferView` natively ([reanimated
+#9475](https://github.com/software-mansion/react-native-reanimated/pull/9475));
+older ones rebuild the view over its whole backing buffer, losing `byteOffset`
+and `length`. Worklets 0.10 in turn requires React Native 0.83+, which is what
+rules out 0.81 and 0.82.
+
+**†** Not verified. 0.8.x and 0.9.x were released before React Native 0.86 and
+are maintained on the `legacy` dist-tag only.
+
+
+
+## Expo SDK
+
+`npx expo install` picks the `react-native-worklets` an SDK bundles, which is
+older than this library needs on SDK 55 and 56. Both work once you ask for the
+versions below explicitly — Reanimated pins worklets exactly, so it moves with
+it.
+
+
+
+
+
+ | Expo SDK |
+ React Native |
+ Bundled worklets |
+ react-native-executorch 0.10.x |
+
+
+
+
+ 54 |
+ 0.81 |
+ 0.5.1 |
+ no |
+
+
+ 55 |
+ 0.83 |
+ 0.7.4 |
+ needs explicit versions |
+
+
+ 56 |
+ 0.85 |
+ 0.8.3 |
+ needs explicit versions |
+
+
+ 57 |
+ 0.86 |
+ 0.10.1 |
yes |
+
+
+
+On SDK 55 and 56, install both:
+
+```bash
+npm install react-native-worklets@^0.10.0 react-native-reanimated@^4.5.0
+```
+
+SDK 54 is React Native 0.81, below what worklets 0.10 accepts, so no
+combination of versions works there.
+
+
diff --git a/docs/src/components/CompatibilityTable/styles.module.css b/docs/src/components/CompatibilityTable/styles.module.css
index a54f35849d..becc300f75 100644
--- a/docs/src/components/CompatibilityTable/styles.module.css
+++ b/docs/src/components/CompatibilityTable/styles.module.css
@@ -56,3 +56,22 @@
background-color: var(--swm-red-dark-140);
color: var(--swm-red-dark-80);
}
+
+.partial {
+ background-color: var(--swm-yellow-light-40);
+ color: var(--swm-yellow-dark-120);
+ border-radius: 4px;
+ padding: 4px 8px;
+ font-weight: 600;
+ font-size: 0.85rem;
+}
+
+[data-theme='dark'] .partial {
+ background-color: var(--swm-yellow-dark-140);
+ color: var(--swm-yellow-dark-80);
+}
+
+.footnote {
+ margin-top: 0.75rem;
+ font-size: 0.85rem;
+}
diff --git a/packages/react-native-executorch/__tests__/api/workletsVersionRange.test.ts b/packages/react-native-executorch/__tests__/api/workletsVersionRange.test.ts
new file mode 100644
index 0000000000..56187da90d
--- /dev/null
+++ b/packages/react-native-executorch/__tests__/api/workletsVersionRange.test.ts
@@ -0,0 +1,89 @@
+/**
+ * The supported `react-native-worklets` range, and the documentation of it.
+ *
+ * This range is not a formality. Worklets 0.10.0 is the first release to
+ * serialize an `ArrayBufferView` natively ([reanimated
+ * #9475](https://github.com/software-mansion/react-native-reanimated/pull/9475));
+ * before it, a view was rebuilt on the target runtime over its whole backing
+ * buffer, so a window into a larger one — a slice of a recording, a pooled
+ * camera frame — reached the model silently widened. Measured on a simulator,
+ * `new Uint8Array(buffer, 64, 16)` arrives with `length` 1024 on worklets 0.8.3
+ * and 16 on 0.10.4.
+ *
+ * Worklets 0.10 in turn requires React Native 0.83+, so the peer range is also
+ * what decides which React Native versions this library supports. That is
+ * exactly the pair that drifted before: the requirements page claimed React
+ * Native 0.81+ while the peer ruled it out, and nothing noticed.
+ *
+ * So the range is checked against three things at once: the version actually
+ * installed here, the React Native floor it implies, and the number written in
+ * the docs.
+ */
+import { readFileSync } from 'fs';
+import { join } from 'path';
+
+import { satisfies, minVersion, subset } from 'semver';
+
+const PACKAGE_ROOT = join(__dirname, '..', '..');
+const DOCS = join(PACKAGE_ROOT, '..', '..', 'docs', 'docs');
+
+const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
+const range: string = manifest.peerDependencies['react-native-worklets'];
+
+/** The worklets copy this repo installs, i.e. what the suites run against. */
+const installed = JSON.parse(
+ readFileSync(
+ require.resolve('react-native-worklets/package.json', {
+ paths: [PACKAGE_ROOT],
+ }),
+ 'utf8'
+ )
+);
+
+describe('react-native-worklets peer range', () => {
+ it('starts at 0.10.0, where native ArrayBufferView serialization landed', () => {
+ expect(satisfies('0.9.3', range)).toBe(false);
+ expect(satisfies('0.10.0', range)).toBe(true);
+ });
+
+ it('covers the releases published since', () => {
+ // A range that stops below the current release leaves anyone installing
+ // `react-native-worklets` with an unmet peer warning for no reason.
+ for (const version of ['0.10.4', '0.11.4', '0.12.2']) {
+ expect(satisfies(version, range)).toBe(true);
+ }
+ });
+
+ it('is bounded, so an unreleased major cannot be assumed to work', () => {
+ expect(subset(range, '>=0.10.0 <0.13.0')).toBe(true);
+ });
+
+ it('is satisfied by the version installed in this repo', () => {
+ expect(satisfies(installed.version, range)).toBe(true);
+ });
+
+ it('matches the React Native floor the docs promise', () => {
+ // Worklets declares which React Native versions it accepts; ours cannot be
+ // lower than what the oldest worklets we allow will run on.
+ const oldest = minVersion(range)!.version;
+ const workletsRnRange: string = installed.peerDependencies['react-native'];
+ const rnFloor = minVersion(workletsRnRange)!.version;
+
+ const gettingStarted = readFileSync(
+ join(DOCS, '01-fundamentals', '01-getting-started.md'),
+ 'utf8'
+ );
+
+ expect(oldest).toBe('0.10.0');
+ // e.g. "React Native 0.83+"
+ expect(gettingStarted).toContain(`React Native ${rnFloor.split('.').slice(0, 2).join('.')}+`);
+ });
+
+ it('is written into the requirements the same way', () => {
+ const gettingStarted = readFileSync(
+ join(DOCS, '01-fundamentals', '01-getting-started.md'),
+ 'utf8'
+ );
+ expect(gettingStarted).toContain(range);
+ });
+});
diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json
index f7ef58035f..6d9e4852d4 100644
--- a/packages/react-native-executorch/package.json
+++ b/packages/react-native-executorch/package.json
@@ -142,7 +142,7 @@
"react": "*",
"react-native": "*",
"react-native-blob-util": "^0.24.0",
- "react-native-worklets": "^0.10.0"
+ "react-native-worklets": ">=0.10.0 <0.13.0"
},
"peerDependenciesMeta": {
"@kesha-antonov/react-native-background-downloader": {
diff --git a/yarn.lock b/yarn.lock
index e40dc3592e..9ce1d3e437 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16506,7 +16506,7 @@ __metadata:
react: "*"
react-native: "*"
react-native-blob-util: ^0.24.0
- react-native-worklets: ^0.10.0
+ react-native-worklets: ">=0.10.0 <0.13.0"
peerDependenciesMeta:
"@kesha-antonov/react-native-background-downloader":
optional: true