diff --git a/bun.lock b/bun.lock
index 2b44f9d2..7323f8d0 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "react-native-evals",
@@ -42,6 +43,7 @@
"@react-navigation/native": "^7.1.0",
"@react-navigation/native-stack": "^7.3.0",
"@shopify/flash-list": "2.0.2",
+ "@shopify/react-native-skia": "^2.11.2",
"@tanstack/react-query": "^5.90.21",
"expo": "~56.0.12",
"expo-audio": "~56.0.12",
@@ -554,6 +556,8 @@
"@shopify/flash-list": ["@shopify/flash-list@2.0.2", "", { "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w=="],
+ "@shopify/react-native-skia": ["@shopify/react-native-skia@2.11.2", "", { "dependencies": { "canvaskit-wasm": "0.41.0", "react-native-skia-android": "152.0.0", "react-native-skia-apple-ios": "152.0.0", "react-native-skia-apple-macos": "152.0.0", "react-native-skia-apple-tvos": "152.0.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "react": ">=19.0", "react-native": ">=0.78", "react-native-reanimated": ">=4.0.0", "react-native-worklets": ">=0.7.0" }, "optionalPeers": ["react-native", "react-native-reanimated", "react-native-worklets"], "bin": { "setup-skia-web": "scripts/setup-canvaskit.js" } }, "sha512-BGTSMlsl3gM/gw9FyfCDgEATBQvNkhyDX9iEDu/iu3ZLcZT7+QzeFwpC0Sij2pLjuY33x3UDPS2DhSis7Mo7Xw=="],
+
"@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
"@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
@@ -690,6 +694,8 @@
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
+ "@webgpu/types": ["@webgpu/types@0.1.21", "", {}, "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow=="],
+
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
@@ -822,6 +828,8 @@
"caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="],
+ "canvaskit-wasm": ["canvaskit-wasm@0.41.0", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-cnbL02NFB3yOYMF/MtxViZHgD1vh55Pvy+zR8q4JuFvyCPejZP3eClkt2GuZ0S7jOmGMCJXaHBasbMChbR9JZg=="],
+
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -1712,8 +1720,18 @@
"react-native-screens": ["react-native-screens@4.25.2", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": ">=0.82.0" } }, "sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg=="],
+ "react-native-skia-android": ["react-native-skia-android@152.0.0", "", {}, "sha512-X++QG8bpPLHpavUyNfQvAxvtjH3r8fnivxS1PTFZsKL4ZiAJ1V9IVsJi4qltafWJbzhq7IutpZQNYd06uwNkjg=="],
+
+ "react-native-skia-apple-ios": ["react-native-skia-apple-ios@152.0.0", "", {}, "sha512-rmnHYWkwvxm7B9fXZCkTRdZVoQwVnNydjytfphxatweQmEqwYE+/g1LOL/emhM/ulmwAFKFT0VNAEzAlvHId2A=="],
+
+ "react-native-skia-apple-macos": ["react-native-skia-apple-macos@152.0.0", "", {}, "sha512-boWCDyPPJUuKwcE+DU8X7dop6EwLdrOH+v7pxYAj+43NbOwgfV1wzq4g+kWIQqag5ZOQInq2mVwCc8S1eJjlXw=="],
+
+ "react-native-skia-apple-tvos": ["react-native-skia-apple-tvos@152.0.0", "", {}, "sha512-+UnXke4Yz9Jmy3uTcDbYFrVG/+CZMqYbaTV3f52wovaMzf8wWy+iZampZr6dW6rOWLWrsUFiq7yMImg6UGbiFg=="],
+
"react-native-worklets": ["react-native-worklets@0.7.4", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag=="],
+ "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
+
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
@@ -2276,6 +2294,8 @@
"react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+ "react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
+
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
diff --git a/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx b/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx
index 77c16c5a..7a60ae2c 100644
--- a/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx
+++ b/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx
@@ -18,7 +18,9 @@ const fontStyle = {
const font = matchFont(fontStyle)
function CanvasContent() {
- const { width, height } = useCanvasSize()
+ const {
+ size: { width, height },
+ } = useCanvasSize()
const label = `${Math.round(width)} × ${Math.round(height)}`
const textX = width / 2 - (font?.getTextWidth(label) ?? 0) / 2
diff --git a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx
index 1c42e5e6..761da1e5 100644
--- a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx
+++ b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx
@@ -20,7 +20,9 @@ const font = matchFont(fontStyle)
const LABEL = 'Hello, Skia!'
function CenteredText() {
- const { width, height } = useCanvasSize()
+ const {
+ size: { width, height },
+ } = useCanvasSize()
const textWidth = font?.getTextWidth(LABEL) ?? 0
const x = (width - textWidth) / 2
const y = height / 2 + FONT_SIZE / 2
diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md
index b87d2b22..38597ef5 100644
--- a/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md
@@ -1 +1 @@
-Demonstrate two clipping techniques on a Skia canvas: use ClipRect to restrict a gradient fill to a rectangular region, and use ClipPath to clip a solid-color rectangle to a non-rectangular polygon or star shape defined by an SVG path string.
+Demonstrate two clipping techniques on a Skia canvas using the `clip` prop on `Group`: clip a gradient fill to a rectangular region, and clip a solid-color rectangle to a non-rectangular polygon or star shape defined by an SVG path string.
diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx b/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx
index 5e4d6999..d1b38aa1 100644
--- a/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx
@@ -1,12 +1,11 @@
import {
Canvas,
- ClipPath,
- ClipRect,
Fill,
Group,
LinearGradient,
Rect,
Skia,
+ rect,
vec,
} from '@shopify/react-native-skia'
@@ -19,9 +18,8 @@ export default function App() {
diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml
index 5e437f64..71a16c31 100644
--- a/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml
@@ -7,10 +7,10 @@ requirements:
description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
weight: 0.1
- id: uses-clip-rect
- description: Must use the ClipRect component from @shopify/react-native-skia to clip a drawing to a rectangular region.
+ description: Must use the `clip` prop on a Group component with a rectangle (e.g. from `rect()` or an `{ x, y, width, height }` object) to clip a drawing to a rectangular region.
- id: uses-clip-path
- description: Must use the ClipPath component from @shopify/react-native-skia with a path or SVG path string.
+ description: Must use the `clip` prop on a Group component with a path (an SkPath or SVG path string) to clip a drawing to a non-rectangular shape.
- id: clip-path-is-non-rectangular
- description: The ClipPath shape must be a non-rectangular polygon, star, or curve — not just a simple rectangle.
+ description: The path-based clip shape must be a non-rectangular polygon, star, or curve — not just a simple rectangle.
- id: both-clips-applied-to-distinct-drawings
- description: ClipRect and ClipPath must each be applied to a different visual element so both clipping techniques are demonstrated independently.
+ description: The rectangular clip and the path clip must each be applied to a different visual element (separate Group components) so both clipping techniques are demonstrated independently.
diff --git a/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx b/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx
index 0b4930ec..cb46cec8 100644
--- a/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx
+++ b/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx
@@ -23,7 +23,7 @@ export default function App() {
paint.setColor(Skia.Color(FIRST_RECT_COLOR))
canvas.save()
canvas.translate(CX, 90)
- canvas.rotate(FIRST_RECT_ROTATION_DEG)
+ canvas.rotate(FIRST_RECT_ROTATION_DEG, 0, 0)
canvas.drawRect(
{
x: -RECT_WIDTH / 2,
@@ -41,7 +41,7 @@ export default function App() {
paint.setColor(Skia.Color(THIRD_RECT_COLOR))
canvas.save()
canvas.translate(CX, 230)
- canvas.rotate(THIRD_RECT_ROTATION_DEG)
+ canvas.rotate(THIRD_RECT_ROTATION_DEG, 0, 0)
canvas.drawRect(
{
x: -RECT_WIDTH / 2,
diff --git a/evals/skia/README.md b/evals/skia/README.md
index 6cefe82b..c741036d 100644
--- a/evals/skia/README.md
+++ b/evals/skia/README.md
@@ -37,7 +37,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D
| 10 | Capture canvas output with `makeImageSnapshot()` via `useCanvasRef`; call `encodeToBytes()` for raw pixel data | canvas/overview |
| 11 | Use `matchFont` with a `fontStyle` object for system font resolution; the Text `y` origin is the text baseline, not the top | text/text |
| 12 | Apply blend modes at the `Group` level with the `blendMode` prop to composite child elements | paint/overview |
-| 13 | Use `ClipRect` / `ClipPath` as children of a `Group` or drawing element to mask content | canvas/overview |
+| 13 | Use the `clip` prop on `Group` (rect, rrect, or path) to mask content; `invertClip` reverses it | group |
| 14 | Use `SweepGradient` / `TwoPointConicalGradient` for angular and conical fills beyond linear/radial | shaders/gradients |
| 15 | Apply effects to a group composite with the `layer` prop (`` + image filters), not per-child filters alone | group |
| 16 | Use `Skia.ParagraphBuilder` + `` for multi-style text layouts; call `layout(width)` before rendering | text/paragraph |
@@ -86,7 +86,7 @@ The pack now covers three built-in gradient shaders (linear, radial, sweep). Maj
| Canvas sizing (UI thread) | `onSize` shared value prop | Partially covered by `useCanvasSize` in eval 01 |
| Per-element gestures | `Animated.View` overlay tracking | Documented in rule 4; eval 14 only tests canvas-level pan |
| Nested image shaders | `ImageShader` inside custom `Shader` | Advanced; needs bundled image asset |
-| Fitbox / Group clip props | `FitBox`, `Group clip` / `invertClip` | Overlap path/SVG evals; `FitBox` is a strong future add |
+| FitBox | `FitBox` | Overlaps path/SVG evals; a strong future add |
| Path / mask filters | `DashPathEffect`, `BlurMask`, morphology | Lower-level paint modifiers; narrow use cases |
## Common issue clusters
diff --git a/testbench/package.json b/testbench/package.json
index aacc0c4e..26526649 100644
--- a/testbench/package.json
+++ b/testbench/package.json
@@ -24,6 +24,7 @@
"@react-navigation/native": "^7.1.0",
"@react-navigation/native-stack": "^7.3.0",
"@shopify/flash-list": "2.0.2",
+ "@shopify/react-native-skia": "^2.11.2",
"@tanstack/react-query": "^5.90.21",
"expo": "~56.0.12",
"expo-audio": "~56.0.12",