diff --git a/evals/skia/01-rn-skia-canvas-fill-background/app/App.tsx b/evals/skia/01-rn-skia-canvas-fill-background/app/App.tsx
new file mode 100644
index 0000000..b54b54e
--- /dev/null
+++ b/evals/skia/01-rn-skia-canvas-fill-background/app/App.tsx
@@ -0,0 +1,13 @@
+import { Platform } from 'react-native'
+
+const FONT_SIZE = 18
+
+const fontStyle = {
+ fontFamily: Platform.select({ ios: 'Helvetica', default: 'serif' }),
+ fontSize: FONT_SIZE,
+ fontWeight: 'bold',
+} as const
+
+export default function App() {
+ return <>>
+}
diff --git a/evals/skia/01-rn-skia-canvas-fill-background/prompt.md b/evals/skia/01-rn-skia-canvas-fill-background/prompt.md
new file mode 100644
index 0000000..96c1df7
--- /dev/null
+++ b/evals/skia/01-rn-skia-canvas-fill-background/prompt.md
@@ -0,0 +1 @@
+Implement a full-screen Skia canvas with a solid background color using Fill. Display the current canvas width and height as centered text inside the canvas. Use useCanvasSize to read the canvas dimensions on the JS thread and position the text accordingly.
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
new file mode 100644
index 0000000..3bd2bed
--- /dev/null
+++ b/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx
@@ -0,0 +1,35 @@
+import { Platform } from 'react-native'
+import { Canvas, Fill, Text, matchFont, useCanvasSize } from '@shopify/react-native-skia'
+
+const FONT_SIZE = 18
+
+const fontStyle = {
+ fontFamily: Platform.select({ ios: 'Helvetica', default: 'serif' }),
+ fontSize: FONT_SIZE,
+ fontWeight: 'bold',
+} as const
+
+const font = matchFont(fontStyle)
+
+function CanvasContent() {
+ const { width, height } = useCanvasSize()
+
+ const label = `${Math.round(width)} × ${Math.round(height)}`
+ const textX = width / 2 - (font?.getTextWidth(label) ?? 0) / 2
+ const textY = height / 2 + FONT_SIZE / 2
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml b/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml
new file mode 100644
index 0000000..39e352e
--- /dev/null
+++ b/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml
@@ -0,0 +1,13 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ - id: uses-fill-for-background
+ description: Must use the Fill component from @shopify/react-native-skia to paint the canvas background color.
+ - id: uses-canvas-size-hook
+ description: Must use useCanvasSize from @shopify/react-native-skia to read canvas dimensions on the JS thread.
+ - id: displays-dimensions-as-text
+ description: Must render the canvas width and height as visible text inside the canvas using the Text component from @shopify/react-native-skia.
diff --git a/evals/skia/02-rn-skia-shape-primitives/app/App.tsx b/evals/skia/02-rn-skia-shape-primitives/app/App.tsx
new file mode 100644
index 0000000..47facab
--- /dev/null
+++ b/evals/skia/02-rn-skia-shape-primitives/app/App.tsx
@@ -0,0 +1,12 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const PADDING = 24
+const SHAPE_SIZE = 80
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/02-rn-skia-shape-primitives/prompt.md b/evals/skia/02-rn-skia-shape-primitives/prompt.md
new file mode 100644
index 0000000..70ab3a8
--- /dev/null
+++ b/evals/skia/02-rn-skia-shape-primitives/prompt.md
@@ -0,0 +1 @@
+Draw a composition of basic shapes on a Skia canvas: a filled rectangle, a filled circle, a rounded rectangle, and a straight line. Give each shape a distinct color and arrange them so they are all visible without overlap.
diff --git a/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx b/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx
new file mode 100644
index 0000000..a3c28a7
--- /dev/null
+++ b/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx
@@ -0,0 +1,54 @@
+import {
+ Canvas,
+ Circle,
+ Fill,
+ Line,
+ Rect,
+ RoundedRect,
+ vec,
+} from '@shopify/react-native-skia'
+
+const PADDING = 24
+const SHAPE_SIZE = 80
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/02-rn-skia-shape-primitives/requirements.yaml b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml
new file mode 100644
index 0000000..a611161
--- /dev/null
+++ b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml
@@ -0,0 +1,18 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-rect-component
+ description: Must render a Rect component from @shopify/react-native-skia.
+ - id: uses-circle-component
+ description: Must render a Circle component from @shopify/react-native-skia.
+ - id: uses-rounded-rect-component
+ description: Must render a RoundedRect or equivalent rounded rectangle component from @shopify/react-native-skia.
+ - id: uses-line-component
+ description: Must render a Line component from @shopify/react-native-skia.
+ - id: shapes-have-distinct-colors
+ description: Each shape must use a visually distinct color.
diff --git a/evals/skia/03-rn-skia-path-drawing/app/App.tsx b/evals/skia/03-rn-skia-path-drawing/app/App.tsx
new file mode 100644
index 0000000..6988c74
--- /dev/null
+++ b/evals/skia/03-rn-skia-path-drawing/app/App.tsx
@@ -0,0 +1,11 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const STROKE_WIDTH = 4
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/03-rn-skia-path-drawing/prompt.md b/evals/skia/03-rn-skia-path-drawing/prompt.md
new file mode 100644
index 0000000..3722baf
--- /dev/null
+++ b/evals/skia/03-rn-skia-path-drawing/prompt.md
@@ -0,0 +1 @@
+Draw a custom Bezier curve path on a Skia canvas. Build the path imperatively using Skia.Path.Make() with moveTo and cubicTo commands, and render it as a stroked line with a visible stroke width and color.
diff --git a/evals/skia/03-rn-skia-path-drawing/reference/App.tsx b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx
new file mode 100644
index 0000000..ebaac7f
--- /dev/null
+++ b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx
@@ -0,0 +1,27 @@
+import { Canvas, Fill, Path, Skia } from '@shopify/react-native-skia'
+
+const STROKE_WIDTH = 4
+
+const path = (() => {
+ const p = Skia.Path.Make()
+ p.moveTo(40, 300)
+ p.cubicTo(100, 100, 260, 500, 340, 200)
+ p.cubicTo(380, 80, 300, 400, 360, 340)
+ return p
+})()
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/03-rn-skia-path-drawing/requirements.yaml b/evals/skia/03-rn-skia-path-drawing/requirements.yaml
new file mode 100644
index 0000000..6948925
--- /dev/null
+++ b/evals/skia/03-rn-skia-path-drawing/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-path-component
+ description: Must render a Path component from @shopify/react-native-skia.
+ - id: builds-path-imperatively
+ description: Must build the path using Skia.Path.Make() with at least moveTo and cubicTo or quadTo path commands.
+ - id: renders-as-stroke
+ description: Must render the path as a stroke using style="stroke" or strokeWidth property, not as a fill.
+ - id: path-is-memoized
+ description: The path object must be created outside of the render function or memoized with useMemo to avoid recreating it on every render.
diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx b/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx
new file mode 100644
index 0000000..09a8ea0
--- /dev/null
+++ b/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx
@@ -0,0 +1,11 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const SIZE = 300
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/prompt.md b/evals/skia/04-rn-skia-paint-stroke-fill/prompt.md
new file mode 100644
index 0000000..100fd41
--- /dev/null
+++ b/evals/skia/04-rn-skia-paint-stroke-fill/prompt.md
@@ -0,0 +1 @@
+Render a circle that has a solid fill and two concentric strokes of different widths using nested Paint components. Wrap multiple shapes in a Group to demonstrate paint attribute inheritance, where the group-level color is shared by all children.
diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx
new file mode 100644
index 0000000..7713295
--- /dev/null
+++ b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx
@@ -0,0 +1,37 @@
+import {
+ Canvas,
+ Circle,
+ Fill,
+ Group,
+ Paint,
+ Rect,
+} from '@shopify/react-native-skia'
+
+const SIZE = 300
+const CX = SIZE / 2
+const R = 100
+const OUTER_STROKE = 16
+const INNER_STROKE = 8
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml
new file mode 100644
index 0000000..8df059f
--- /dev/null
+++ b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml
@@ -0,0 +1,14 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-paint-children-for-strokes
+ description: Must use Paint components as children of a drawing element to add at least two strokes with different widths.
+ - id: paint-uses-stroke-style
+ description: At least one Paint child must explicitly use style="stroke" with a strokeWidth property.
+ - id: uses-group-paint-inheritance
+ description: Must use a Group component with a color or paint prop to demonstrate that paint attributes are inherited by child drawing elements.
diff --git a/evals/skia/05-rn-skia-linear-gradient/app/App.tsx b/evals/skia/05-rn-skia-linear-gradient/app/App.tsx
new file mode 100644
index 0000000..5bb05d5
--- /dev/null
+++ b/evals/skia/05-rn-skia-linear-gradient/app/App.tsx
@@ -0,0 +1,6 @@
+const START_COLORS = ['#6366f1', '#3b82f6', '#06b6d4', '#10b981']
+const END_COLORS = ['#f59e0b', '#ef4444', '#ec4899', '#8b5cf6']
+
+export default function App() {
+ return <>>
+}
diff --git a/evals/skia/05-rn-skia-linear-gradient/prompt.md b/evals/skia/05-rn-skia-linear-gradient/prompt.md
new file mode 100644
index 0000000..a784ca2
--- /dev/null
+++ b/evals/skia/05-rn-skia-linear-gradient/prompt.md
@@ -0,0 +1 @@
+Fill a Skia canvas with an animated linear gradient that continuously cycles through two sets of colors. Use LinearGradient as a child of Fill and drive the color interpolation with a Reanimated shared value combined with Skia's interpolateColors function.
diff --git a/evals/skia/05-rn-skia-linear-gradient/reference/App.tsx b/evals/skia/05-rn-skia-linear-gradient/reference/App.tsx
new file mode 100644
index 0000000..9c47947
--- /dev/null
+++ b/evals/skia/05-rn-skia-linear-gradient/reference/App.tsx
@@ -0,0 +1,39 @@
+import { useEffect } from 'react'
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Fill, LinearGradient, interpolateColors, vec } from '@shopify/react-native-skia'
+import { useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
+
+const START_COLORS = ['#6366f1', '#3b82f6', '#06b6d4', '#10b981']
+const END_COLORS = ['#f59e0b', '#ef4444', '#ec4899', '#8b5cf6']
+
+const INPUT_RANGE = START_COLORS.map((_, i) => i)
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const progress = useSharedValue(0)
+
+ useEffect(() => {
+ progress.value = withRepeat(
+ withTiming(START_COLORS.length - 1, { duration: 3000 }),
+ -1,
+ true
+ )
+ }, [progress])
+
+ const colors = useDerivedValue(() => [
+ interpolateColors(progress.value, INPUT_RANGE, START_COLORS),
+ interpolateColors(progress.value, INPUT_RANGE, END_COLORS),
+ ])
+
+ return (
+
+ )
+}
diff --git a/evals/skia/05-rn-skia-linear-gradient/requirements.yaml b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml
new file mode 100644
index 0000000..d89553b
--- /dev/null
+++ b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-linear-gradient-component
+ description: Must use LinearGradient from @shopify/react-native-skia as a child shader of Fill or another drawing element.
+ - id: uses-skia-interpolate-colors
+ description: Must use interpolateColors from @shopify/react-native-skia for color transitions, not interpolateColor from react-native-reanimated.
+ - id: uses-reanimated-shared-value
+ description: Must use useSharedValue from react-native-reanimated to drive the gradient animation progress.
+ - id: animation-is-infinite-loop
+ description: Must use withRepeat to create a continuous looping color animation.
diff --git a/evals/skia/06-rn-skia-radial-gradient/app/App.tsx b/evals/skia/06-rn-skia-radial-gradient/app/App.tsx
new file mode 100644
index 0000000..5657f42
--- /dev/null
+++ b/evals/skia/06-rn-skia-radial-gradient/app/App.tsx
@@ -0,0 +1,11 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const SIZE = 320
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/06-rn-skia-radial-gradient/prompt.md b/evals/skia/06-rn-skia-radial-gradient/prompt.md
new file mode 100644
index 0000000..1072ef8
--- /dev/null
+++ b/evals/skia/06-rn-skia-radial-gradient/prompt.md
@@ -0,0 +1 @@
+Draw a circle filled with a radial gradient using the RadialGradient component as a child shader. The gradient should radiate from the circle's center, transitioning from a bright opaque inner color to a fully transparent outer edge.
diff --git a/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx b/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx
new file mode 100644
index 0000000..c9b34a5
--- /dev/null
+++ b/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx
@@ -0,0 +1,29 @@
+import {
+ Canvas,
+ Circle,
+ Fill,
+ RadialGradient,
+ vec,
+} from '@shopify/react-native-skia'
+
+const SIZE = 320
+const CX = SIZE / 2
+const CY = SIZE / 2
+const R = 120
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/06-rn-skia-radial-gradient/requirements.yaml b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml
new file mode 100644
index 0000000..c2a9059
--- /dev/null
+++ b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml
@@ -0,0 +1,14 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-radial-gradient-component
+ description: Must use the RadialGradient component from @shopify/react-native-skia as a child of a drawing element.
+ - id: gradient-center-matches-shape-center
+ description: The RadialGradient center coordinates (cx, cy) must match the center of the shape it fills.
+ - id: uses-transparency-in-gradient
+ description: Must include at least one gradient color stop with an alpha (transparency) value, such as 'transparent' or a color with 0 alpha.
diff --git a/evals/skia/07-rn-skia-image-display/app/App.tsx b/evals/skia/07-rn-skia-image-display/app/App.tsx
new file mode 100644
index 0000000..07d04f6
--- /dev/null
+++ b/evals/skia/07-rn-skia-image-display/app/App.tsx
@@ -0,0 +1,14 @@
+import { StyleSheet } from 'react-native'
+
+const CANVAS_SIZE = 320
+const IMAGE_URL = 'https://picsum.photos/320/320'
+
+export default function App() {
+ return <>>
+}
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: '#0f172a',
+ },
+})
diff --git a/evals/skia/07-rn-skia-image-display/prompt.md b/evals/skia/07-rn-skia-image-display/prompt.md
new file mode 100644
index 0000000..c370421
--- /dev/null
+++ b/evals/skia/07-rn-skia-image-display/prompt.md
@@ -0,0 +1 @@
+Load an image using the useImage hook and display it on a Skia canvas using the Image component with the "cover" fit mode. Handle the loading state gracefully by rendering a placeholder rectangle while the image is not yet available.
diff --git a/evals/skia/07-rn-skia-image-display/reference/App.tsx b/evals/skia/07-rn-skia-image-display/reference/App.tsx
new file mode 100644
index 0000000..28c471d
--- /dev/null
+++ b/evals/skia/07-rn-skia-image-display/reference/App.tsx
@@ -0,0 +1,47 @@
+import { StyleSheet, View } from 'react-native'
+import { Canvas, Image, Rect, useImage } from '@shopify/react-native-skia'
+
+const CANVAS_SIZE = 320
+const IMAGE_URL = 'https://picsum.photos/320/320'
+
+export default function App() {
+ const image = useImage(IMAGE_URL)
+
+ return (
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#0f172a',
+ },
+ canvas: {
+ width: CANVAS_SIZE,
+ height: CANVAS_SIZE,
+ },
+})
diff --git a/evals/skia/07-rn-skia-image-display/requirements.yaml b/evals/skia/07-rn-skia-image-display/requirements.yaml
new file mode 100644
index 0000000..3017e33
--- /dev/null
+++ b/evals/skia/07-rn-skia-image-display/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-use-image-hook
+ description: Must use the useImage hook from @shopify/react-native-skia to load the image, not React Native's Image component.
+ - id: uses-skia-image-component
+ description: Must render the Image component from @shopify/react-native-skia (not React Native's built-in Image) to draw the image on the canvas.
+ - id: uses-cover-fit-mode
+ description: Must use the fit="cover" prop on the Skia Image component.
+ - id: handles-null-loading-state
+ description: Must handle the null state returned by useImage before the image loads, rendering a placeholder or returning null to avoid a crash.
diff --git a/evals/skia/08-rn-skia-text-rendering/app/App.tsx b/evals/skia/08-rn-skia-text-rendering/app/App.tsx
new file mode 100644
index 0000000..36a3211
--- /dev/null
+++ b/evals/skia/08-rn-skia-text-rendering/app/App.tsx
@@ -0,0 +1,18 @@
+import { Platform } from 'react-native'
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const FONT_SIZE = 18
+
+const fontStyle = {
+ fontFamily: Platform.select({ ios: 'Helvetica', default: 'serif' }),
+ fontSize: FONT_SIZE,
+ fontWeight: 'bold',
+} as const
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/08-rn-skia-text-rendering/prompt.md b/evals/skia/08-rn-skia-text-rendering/prompt.md
new file mode 100644
index 0000000..e59d09c
--- /dev/null
+++ b/evals/skia/08-rn-skia-text-rendering/prompt.md
@@ -0,0 +1 @@
+Render a centered line of text on a Skia canvas using the Text component. Resolve the font from the system using matchFont with a bold font weight. Account for the fact that the Text component's y coordinate refers to the text baseline, not the top edge.
diff --git a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx
new file mode 100644
index 0000000..1c42e5e
--- /dev/null
+++ b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx
@@ -0,0 +1,38 @@
+import { Platform } from 'react-native'
+import {
+ Canvas,
+ Fill,
+ Text,
+ matchFont,
+ useCanvasSize,
+} from '@shopify/react-native-skia'
+
+const FONT_SIZE = 32
+
+const fontStyle = {
+ fontFamily: Platform.select({ ios: 'Helvetica', default: 'serif' }),
+ fontSize: FONT_SIZE,
+ fontWeight: 'bold',
+} as const
+
+const font = matchFont(fontStyle)
+
+const LABEL = 'Hello, Skia!'
+
+function CenteredText() {
+ const { width, height } = useCanvasSize()
+ const textWidth = font?.getTextWidth(LABEL) ?? 0
+ const x = (width - textWidth) / 2
+ const y = height / 2 + FONT_SIZE / 2
+
+ return
+}
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/08-rn-skia-text-rendering/requirements.yaml b/evals/skia/08-rn-skia-text-rendering/requirements.yaml
new file mode 100644
index 0000000..fb6b7e4
--- /dev/null
+++ b/evals/skia/08-rn-skia-text-rendering/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-match-font
+ description: Must use matchFont from @shopify/react-native-skia to resolve the font from the system font manager.
+ - id: font-style-specifies-bold
+ description: The fontStyle object passed to matchFont must include a bold font weight (fontWeight "bold" or "700" or higher).
+ - id: uses-text-component
+ description: Must use the Text component from @shopify/react-native-skia to render the text on the canvas.
+ - id: text-y-accounts-for-baseline
+ description: The y value passed to the Text component must account for the fact that y is the text baseline, not the top edge (y should be at least fontSize to be visible).
diff --git a/evals/skia/09-rn-skia-blur-filter/app/App.tsx b/evals/skia/09-rn-skia-blur-filter/app/App.tsx
new file mode 100644
index 0000000..bcdd2b4
--- /dev/null
+++ b/evals/skia/09-rn-skia-blur-filter/app/App.tsx
@@ -0,0 +1,24 @@
+import { StyleSheet } from 'react-native'
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const BLUR_LEVELS = [0, 2, 6, 14]
+
+export default function App() {
+ return (
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: '#0f172a',
+ },
+ button: {
+ backgroundColor: '#3b82f6',
+ },
+ buttonText: {
+ color: '#fff',
+ },
+})
diff --git a/evals/skia/09-rn-skia-blur-filter/prompt.md b/evals/skia/09-rn-skia-blur-filter/prompt.md
new file mode 100644
index 0000000..442d92e
--- /dev/null
+++ b/evals/skia/09-rn-skia-blur-filter/prompt.md
@@ -0,0 +1 @@
+Apply a Gaussian blur image filter to a rectangle on a Skia canvas using the Blur component. Provide a slider or button to adjust the blur intensity at runtime. Render an unblurred element in the background so the blur effect is clearly visible.
diff --git a/evals/skia/09-rn-skia-blur-filter/reference/App.tsx b/evals/skia/09-rn-skia-blur-filter/reference/App.tsx
new file mode 100644
index 0000000..936c6ee
--- /dev/null
+++ b/evals/skia/09-rn-skia-blur-filter/reference/App.tsx
@@ -0,0 +1,52 @@
+import { useState } from 'react'
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import { Blur, Canvas, Circle, Fill, Rect } from '@shopify/react-native-skia'
+
+const BLUR_LEVELS = [0, 2, 6, 14]
+
+export default function App() {
+ const [blurIndex, setBlurIndex] = useState(1)
+ const blur = BLUR_LEVELS[blurIndex]
+
+ const cycleBlur = () => setBlurIndex((i) => (i + 1) % BLUR_LEVELS.length)
+
+ return (
+
+
+
+
+ Blur: {blur === 0 ? 'off' : blur}
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0f172a',
+ },
+ canvas: {
+ flex: 1,
+ },
+ button: {
+ margin: 24,
+ paddingVertical: 14,
+ borderRadius: 10,
+ backgroundColor: '#3b82f6',
+ alignItems: 'center',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+})
diff --git a/evals/skia/09-rn-skia-blur-filter/requirements.yaml b/evals/skia/09-rn-skia-blur-filter/requirements.yaml
new file mode 100644
index 0000000..7a0bb69
--- /dev/null
+++ b/evals/skia/09-rn-skia-blur-filter/requirements.yaml
@@ -0,0 +1,14 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-blur-component
+ description: Must use the Blur component from @shopify/react-native-skia as a child image filter of a drawing element or Group.
+ - id: blur-amount-is-runtime-controllable
+ description: Must provide a UI control (slider, button, or similar) that changes the blur amount at runtime.
+ - id: unblurred-background-present
+ description: Must render at least one element outside of the blurred element so the blur effect is visually distinguishable.
diff --git a/evals/skia/10-rn-skia-color-matrix-filter/app/App.tsx b/evals/skia/10-rn-skia-color-matrix-filter/app/App.tsx
new file mode 100644
index 0000000..3087505
--- /dev/null
+++ b/evals/skia/10-rn-skia-color-matrix-filter/app/App.tsx
@@ -0,0 +1,19 @@
+import { StyleSheet } from 'react-native'
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+export default function App() {
+ return (
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: '#f8fafc',
+ },
+ button: {
+ backgroundColor: '#64748b',
+ },
+})
diff --git a/evals/skia/10-rn-skia-color-matrix-filter/prompt.md b/evals/skia/10-rn-skia-color-matrix-filter/prompt.md
new file mode 100644
index 0000000..cbabf52
--- /dev/null
+++ b/evals/skia/10-rn-skia-color-matrix-filter/prompt.md
@@ -0,0 +1 @@
+Apply a color matrix filter to a Skia drawing using the ColorMatrix component to desaturate or invert its colors. Use ColorMatrix as a child image filter of a Group or drawing element. Add a button to toggle the filter on and off.
diff --git a/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx b/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx
new file mode 100644
index 0000000..cc4adaa
--- /dev/null
+++ b/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx
@@ -0,0 +1,64 @@
+import { useState } from 'react'
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import {
+ Canvas,
+ Circle,
+ ColorMatrix,
+ Fill,
+ Group,
+} from '@shopify/react-native-skia'
+
+const GRAYSCALE_MATRIX = [
+ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152,
+ 0.0722, 0, 0, 0, 0, 0, 1, 0,
+]
+
+export default function App() {
+ const [filtered, setFiltered] = useState(false)
+
+ return (
+
+
+
+ setFiltered((v) => !v)}
+ >
+
+ Grayscale: {filtered ? 'ON' : 'OFF'}
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f8fafc',
+ },
+ canvas: {
+ flex: 1,
+ },
+ button: {
+ margin: 24,
+ paddingVertical: 14,
+ borderRadius: 10,
+ backgroundColor: '#64748b',
+ alignItems: 'center',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+})
diff --git a/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml
new file mode 100644
index 0000000..0ed8f29
--- /dev/null
+++ b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-color-matrix-component
+ description: Must use the ColorMatrix component from @shopify/react-native-skia.
+ - id: color-matrix-is-child-of-drawing
+ description: ColorMatrix must be used as a child image filter of a Group, Image, or other drawing element — not as a standalone element.
+ - id: filter-can-be-toggled
+ description: Must provide a button or control that enables and disables the ColorMatrix filter at runtime.
+ - id: matrix-produces-visible-change
+ description: The color matrix values must produce a noticeable visual transformation (desaturation, inversion, hue rotation, or similar), not an identity matrix.
diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/app/App.tsx b/evals/skia/11-rn-skia-reanimated-basic-animation/app/App.tsx
new file mode 100644
index 0000000..f4db03a
--- /dev/null
+++ b/evals/skia/11-rn-skia-reanimated-basic-animation/app/App.tsx
@@ -0,0 +1,12 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const RADIUS = 32
+const DURATION_MS = 1200
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/prompt.md b/evals/skia/11-rn-skia-reanimated-basic-animation/prompt.md
new file mode 100644
index 0000000..732da24
--- /dev/null
+++ b/evals/skia/11-rn-skia-reanimated-basic-animation/prompt.md
@@ -0,0 +1 @@
+Animate a circle that bounces horizontally across a Skia canvas. Drive the circle's cx position directly with a Reanimated useSharedValue animated with withRepeat and withTiming. Do not use createAnimatedComponent or useAnimatedProps — pass the shared value directly as a Skia prop.
diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx b/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx
new file mode 100644
index 0000000..e31d541
--- /dev/null
+++ b/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx
@@ -0,0 +1,27 @@
+import { useEffect } from 'react'
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Circle, Fill } from '@shopify/react-native-skia'
+import { useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
+
+const RADIUS = 32
+const DURATION_MS = 1200
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const cx = useSharedValue(RADIUS)
+
+ useEffect(() => {
+ cx.value = withRepeat(
+ withTiming(width - RADIUS, { duration: DURATION_MS }),
+ -1,
+ true
+ )
+ }, [cx, width])
+
+ return (
+
+ )
+}
diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml
new file mode 100644
index 0000000..40cad57
--- /dev/null
+++ b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: shared-value-passed-directly-as-skia-prop
+ description: Must pass a Reanimated useSharedValue directly as a Skia component property (cx, cy, or r) without wrapping it in createAnimatedComponent or useAnimatedProps.
+ - id: uses-with-repeat
+ description: Must use withRepeat from react-native-reanimated to create a looping animation.
+ - id: uses-with-timing
+ description: Must use withTiming from react-native-reanimated to control the animation easing and duration.
+ - id: no-create-animated-component
+ description: Must not use createAnimatedComponent or useAnimatedProps for the Skia component animation.
diff --git a/evals/skia/12-rn-skia-derived-value-animation/app/App.tsx b/evals/skia/12-rn-skia-derived-value-animation/app/App.tsx
new file mode 100644
index 0000000..df41e24
--- /dev/null
+++ b/evals/skia/12-rn-skia-derived-value-animation/app/App.tsx
@@ -0,0 +1,12 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const MIN_R = 20
+const MAX_R = 100
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/12-rn-skia-derived-value-animation/prompt.md b/evals/skia/12-rn-skia-derived-value-animation/prompt.md
new file mode 100644
index 0000000..3a86f7b
--- /dev/null
+++ b/evals/skia/12-rn-skia-derived-value-animation/prompt.md
@@ -0,0 +1 @@
+Create a pulsing animation where both the radius and the vertical position of a circle on a Skia canvas change in sync. Derive both Skia properties from a single Reanimated shared value using useDerivedValue, so one animated source drives multiple visual attributes.
diff --git a/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx b/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx
new file mode 100644
index 0000000..9f62ba1
--- /dev/null
+++ b/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx
@@ -0,0 +1,38 @@
+import { useEffect } from 'react'
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Circle, Fill } from '@shopify/react-native-skia'
+import {
+ useDerivedValue,
+ useSharedValue,
+ withRepeat,
+ withTiming,
+} from 'react-native-reanimated'
+
+const MIN_R = 20
+const MAX_R = 100
+const DURATION_MS = 900
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const progress = useSharedValue(0)
+
+ useEffect(() => {
+ progress.value = withRepeat(
+ withTiming(1, { duration: DURATION_MS }),
+ -1,
+ true
+ )
+ }, [progress])
+
+ const r = useDerivedValue(() => MIN_R + (MAX_R - MIN_R) * progress.value)
+ const cy = useDerivedValue(
+ () => height / 2 + (MAX_R - MIN_R) * (1 - progress.value) * 0.4
+ )
+
+ return (
+
+ )
+}
diff --git a/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml
new file mode 100644
index 0000000..8aed999
--- /dev/null
+++ b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-derived-value
+ description: Must use useDerivedValue from react-native-reanimated to compute one or more Skia property values from a shared value.
+ - id: multiple-props-from-single-source
+ description: At least two different Skia properties (e.g. r and cy) must be derived from the same source shared value.
+ - id: uses-with-timing-or-spring
+ description: Must use withTiming or withSpring to drive the source shared value.
+ - id: animation-loops-continuously
+ description: Must use withRepeat so the animation runs continuously without stopping.
diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/app/App.tsx b/evals/skia/13-rn-skia-animated-color-interpolation/app/App.tsx
new file mode 100644
index 0000000..af637b9
--- /dev/null
+++ b/evals/skia/13-rn-skia-animated-color-interpolation/app/App.tsx
@@ -0,0 +1,5 @@
+const COLORS = ['#6366f1', '#ec4899', '#f59e0b', '#10b981', '#6366f1']
+
+export default function App() {
+ return <>>
+}
diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/prompt.md b/evals/skia/13-rn-skia-animated-color-interpolation/prompt.md
new file mode 100644
index 0000000..d65e821
--- /dev/null
+++ b/evals/skia/13-rn-skia-animated-color-interpolation/prompt.md
@@ -0,0 +1 @@
+Build an animated background that continuously cycles through a sequence of colors using Skia's interpolateColors function. Use useDerivedValue to compute the current color from a Reanimated progress value. Do not use interpolateColor from react-native-reanimated.
diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/reference/App.tsx b/evals/skia/13-rn-skia-animated-color-interpolation/reference/App.tsx
new file mode 100644
index 0000000..f5c128e
--- /dev/null
+++ b/evals/skia/13-rn-skia-animated-color-interpolation/reference/App.tsx
@@ -0,0 +1,29 @@
+import { useEffect } from 'react'
+import { Canvas, Fill, interpolateColors } from '@shopify/react-native-skia'
+import { useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
+
+const COLORS = ['#6366f1', '#ec4899', '#f59e0b', '#10b981', '#6366f1']
+const INPUT_RANGE = COLORS.map((_, i) => i)
+const DURATION_MS = 2000
+
+export default function App() {
+ const progress = useSharedValue(0)
+
+ useEffect(() => {
+ progress.value = withRepeat(
+ withTiming(COLORS.length - 1, { duration: DURATION_MS }),
+ -1,
+ false
+ )
+ }, [progress])
+
+ const color = useDerivedValue(() =>
+ interpolateColors(progress.value, INPUT_RANGE, COLORS)
+ )
+
+ return (
+
+ )
+}
diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml
new file mode 100644
index 0000000..51336e4
--- /dev/null
+++ b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-interpolate-colors-from-skia
+ description: Must use interpolateColors from @shopify/react-native-skia for color transitions, not interpolateColor from react-native-reanimated.
+ - id: uses-derived-value-for-color
+ description: Must use useDerivedValue to compute the animated color value from a shared progress value.
+ - id: animation-is-infinite
+ description: Must use withRepeat with a negative count (or equivalent) so the color cycle runs indefinitely.
+ - id: cycles-through-multiple-colors
+ description: Must define at least three distinct colors in the input range to produce a multi-step color cycle.
diff --git a/evals/skia/14-rn-skia-gesture-pan/app/App.tsx b/evals/skia/14-rn-skia-gesture-pan/app/App.tsx
new file mode 100644
index 0000000..1bd40ba
--- /dev/null
+++ b/evals/skia/14-rn-skia-gesture-pan/app/App.tsx
@@ -0,0 +1,11 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const RADIUS = 36
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/14-rn-skia-gesture-pan/prompt.md b/evals/skia/14-rn-skia-gesture-pan/prompt.md
new file mode 100644
index 0000000..1d57f00
--- /dev/null
+++ b/evals/skia/14-rn-skia-gesture-pan/prompt.md
@@ -0,0 +1 @@
+Make a draggable circle on a Skia canvas. Use react-native-gesture-handler's Pan gesture to update Reanimated shared values for the circle's position. Apply withDecay on gesture end to give the circle a physics-based coast-to-stop inertia effect.
diff --git a/evals/skia/14-rn-skia-gesture-pan/reference/App.tsx b/evals/skia/14-rn-skia-gesture-pan/reference/App.tsx
new file mode 100644
index 0000000..d3025a3
--- /dev/null
+++ b/evals/skia/14-rn-skia-gesture-pan/reference/App.tsx
@@ -0,0 +1,31 @@
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Circle, Fill } from '@shopify/react-native-skia'
+import { Gesture, GestureDetector } from 'react-native-gesture-handler'
+import { useSharedValue, withDecay } from 'react-native-reanimated'
+
+const RADIUS = 36
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const cx = useSharedValue(width / 2)
+ const cy = useSharedValue(height / 2)
+
+ const gesture = Gesture.Pan()
+ .onChange((e) => {
+ cx.value += e.changeX
+ cy.value += e.changeY
+ })
+ .onEnd((e) => {
+ cx.value = withDecay({ velocity: e.velocityX, clamp: [RADIUS, width - RADIUS] })
+ cy.value = withDecay({ velocity: e.velocityY, clamp: [RADIUS, height - RADIUS] })
+ })
+
+ return (
+
+
+
+ )
+}
diff --git a/evals/skia/14-rn-skia-gesture-pan/requirements.yaml b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml
new file mode 100644
index 0000000..cda86e2
--- /dev/null
+++ b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-gesture-detector
+ description: Must wrap the Canvas with GestureDetector from react-native-gesture-handler.
+ - id: uses-pan-gesture-on-change
+ description: Must use Gesture.Pan() with an onChange handler that updates position shared values on the UI thread.
+ - id: uses-with-decay-on-end
+ description: Must apply withDecay in the Pan gesture onEnd handler to produce inertia-based deceleration after the gesture ends.
+ - id: circle-position-driven-by-shared-values
+ description: The draggable circle's cx and cy props must be driven directly by Reanimated shared values.
diff --git a/evals/skia/15-rn-skia-transforms/app/App.tsx b/evals/skia/15-rn-skia-transforms/app/App.tsx
new file mode 100644
index 0000000..6be6ffb
--- /dev/null
+++ b/evals/skia/15-rn-skia-transforms/app/App.tsx
@@ -0,0 +1,9 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/15-rn-skia-transforms/prompt.md b/evals/skia/15-rn-skia-transforms/prompt.md
new file mode 100644
index 0000000..a73120f
--- /dev/null
+++ b/evals/skia/15-rn-skia-transforms/prompt.md
@@ -0,0 +1 @@
+Display a set of shapes inside a Skia Group that continuously rotates around the canvas center. Drive the rotation with a Reanimated shared value passed as the transform prop. Nest a second Group inside the rotating group and apply a scale transform to it to demonstrate transform composition.
diff --git a/evals/skia/15-rn-skia-transforms/reference/App.tsx b/evals/skia/15-rn-skia-transforms/reference/App.tsx
new file mode 100644
index 0000000..c4fcf6b
--- /dev/null
+++ b/evals/skia/15-rn-skia-transforms/reference/App.tsx
@@ -0,0 +1,57 @@
+import { useEffect } from 'react'
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Circle, Fill, Group, Rect } from '@shopify/react-native-skia'
+import { useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
+
+const DURATION_MS = 3000
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const cx = width / 2
+ const cy = height / 2
+
+ const angle = useSharedValue(0)
+
+ useEffect(() => {
+ angle.value = withRepeat(withTiming(Math.PI * 2, { duration: DURATION_MS }), -1, false)
+ }, [angle])
+
+ const outerTransform = useDerivedValue(() => [
+ { translateX: cx },
+ { translateY: cy },
+ { rotate: angle.value },
+ { translateX: -cx },
+ { translateY: -cy },
+ ])
+
+ const innerTransform = useDerivedValue(() => [
+ { translateX: cx },
+ { translateY: cy },
+ { scale: 0.5 },
+ { translateX: -cx },
+ { translateY: -cy },
+ ])
+
+ return (
+
+ )
+}
diff --git a/evals/skia/15-rn-skia-transforms/requirements.yaml b/evals/skia/15-rn-skia-transforms/requirements.yaml
new file mode 100644
index 0000000..de5fe9c
--- /dev/null
+++ b/evals/skia/15-rn-skia-transforms/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-group-with-transform
+ description: Must use a Group component from @shopify/react-native-skia with a transform prop that includes rotation.
+ - id: transform-driven-by-reanimated
+ description: The rotation or transform value must be a Reanimated shared or derived value passed directly as the transform prop.
+ - id: nested-groups-for-transform-composition
+ description: Must use at least two nested Group components where the inner group applies a different transform (e.g. scale) from the outer group.
+ - id: animation-is-continuous
+ description: The rotation animation must run continuously using withRepeat.
diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/app/App.tsx b/evals/skia/16-rn-skia-clip-rect-and-path/app/App.tsx
new file mode 100644
index 0000000..5c1b481
--- /dev/null
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/app/App.tsx
@@ -0,0 +1,12 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const STAR_SVG =
+ 'M 160 30 L 190 110 L 280 110 L 210 160 L 240 240 L 160 190 L 80 240 L 110 160 L 40 110 L 130 110 Z'
+
+export default function App() {
+ return (
+
+ )
+}
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
new file mode 100644
index 0000000..b87d2b2
--- /dev/null
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md
@@ -0,0 +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.
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
new file mode 100644
index 0000000..29b51c7
--- /dev/null
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx
@@ -0,0 +1,30 @@
+import { Canvas, ClipPath, ClipRect, Fill, Group, LinearGradient, Rect, Skia, vec } from '@shopify/react-native-skia'
+
+const STAR_SVG = 'M 160 30 L 190 110 L 280 110 L 210 160 L 240 240 L 160 190 L 80 240 L 110 160 L 40 110 L 130 110 Z'
+const starPath = Skia.Path.MakeFromSVGString(STAR_SVG)!
+
+export default function App() {
+ return (
+
+ )
+}
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
new file mode 100644
index 0000000..5e437f6
--- /dev/null
+++ b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ 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.
+ - id: uses-clip-path
+ description: Must use the ClipPath component from @shopify/react-native-skia with a path or SVG path string.
+ - id: clip-path-is-non-rectangular
+ description: The ClipPath 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.
diff --git a/evals/skia/17-rn-skia-blend-mode/app/App.tsx b/evals/skia/17-rn-skia-blend-mode/app/App.tsx
new file mode 100644
index 0000000..189637f
--- /dev/null
+++ b/evals/skia/17-rn-skia-blend-mode/app/App.tsx
@@ -0,0 +1,9 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/17-rn-skia-blend-mode/prompt.md b/evals/skia/17-rn-skia-blend-mode/prompt.md
new file mode 100644
index 0000000..85df0ff
--- /dev/null
+++ b/evals/skia/17-rn-skia-blend-mode/prompt.md
@@ -0,0 +1 @@
+Render three overlapping circles in cyan, magenta, and yellow arranged like a Venn diagram, placed inside a Skia Group with the multiply blend mode. The overlapping regions should blend into secondary and tertiary colors to demonstrate how Skia blend modes composite layers.
diff --git a/evals/skia/17-rn-skia-blend-mode/reference/App.tsx b/evals/skia/17-rn-skia-blend-mode/reference/App.tsx
new file mode 100644
index 0000000..8cbc93b
--- /dev/null
+++ b/evals/skia/17-rn-skia-blend-mode/reference/App.tsx
@@ -0,0 +1,21 @@
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Circle, Fill, Group } from '@shopify/react-native-skia'
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const cx = width / 2
+ const cy = height / 2
+ const r = 100
+ const offset = 60
+
+ return (
+
+ )
+}
diff --git a/evals/skia/17-rn-skia-blend-mode/requirements.yaml b/evals/skia/17-rn-skia-blend-mode/requirements.yaml
new file mode 100644
index 0000000..4f99560
--- /dev/null
+++ b/evals/skia/17-rn-skia-blend-mode/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-blend-mode-on-group
+ description: Must set the blendMode prop on a Group component from @shopify/react-native-skia to composite the child elements.
+ - id: uses-multiply-blend-mode
+ description: The blendMode must be "multiply" (or another subtractive/compositing mode such as "screen" or "overlay"), not "normal".
+ - id: three-overlapping-circles
+ description: Must render at least three circle elements with overlapping regions so the blend mode visibly affects the intersection areas.
+ - id: circles-use-primary-colors
+ description: The circles should use cyan, magenta, and yellow (or equivalent primary colors) to produce recognizable secondary blended colors.
diff --git a/evals/skia/18-rn-skia-svg-path-rendering/app/App.tsx b/evals/skia/18-rn-skia-svg-path-rendering/app/App.tsx
new file mode 100644
index 0000000..60a72d6
--- /dev/null
+++ b/evals/skia/18-rn-skia-svg-path-rendering/app/App.tsx
@@ -0,0 +1,12 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const HEART_SVG =
+ 'M 128 96 C 128 96 80 40 32 72 C -8 96 16 160 64 192 L 128 240 L 192 192 C 240 160 264 96 224 72 C 176 40 128 96 128 96 Z'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/18-rn-skia-svg-path-rendering/prompt.md b/evals/skia/18-rn-skia-svg-path-rendering/prompt.md
new file mode 100644
index 0000000..696a94e
--- /dev/null
+++ b/evals/skia/18-rn-skia-svg-path-rendering/prompt.md
@@ -0,0 +1 @@
+Render a heart or star shape on a Skia canvas by parsing an SVG path string with Skia.Path.MakeFromSVGString. Fill the shape with a linear gradient and add a visible stroke outline around it.
diff --git a/evals/skia/18-rn-skia-svg-path-rendering/reference/App.tsx b/evals/skia/18-rn-skia-svg-path-rendering/reference/App.tsx
new file mode 100644
index 0000000..80a3b64
--- /dev/null
+++ b/evals/skia/18-rn-skia-svg-path-rendering/reference/App.tsx
@@ -0,0 +1,30 @@
+import { Canvas, Fill, LinearGradient, Path, Skia, vec } from '@shopify/react-native-skia'
+
+const HEART_SVG =
+ 'M 128 96 C 128 96 80 40 32 72 C -8 96 16 160 64 192 L 128 240 L 192 192 C 240 160 264 96 224 72 C 176 40 128 96 128 96 Z'
+
+const heartPath = Skia.Path.MakeFromSVGString(HEART_SVG)!
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml
new file mode 100644
index 0000000..9ff017b
--- /dev/null
+++ b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-path-make-from-svg-string
+ description: Must use Skia.Path.MakeFromSVGString to parse an SVG path string into a SkPath object.
+ - id: svg-path-is-non-trivial-shape
+ description: The SVG path string must represent a recognizable non-rectangular shape such as a heart, star, or arrow — not just a simple rectangle or line.
+ - id: path-has-gradient-fill
+ description: Must render the path with a LinearGradient or RadialGradient as a child fill shader, not a flat color fill.
+ - id: path-has-stroke-outline
+ description: Must render a separate stroked outline of the same path using style="stroke" and a visible strokeWidth.
diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/app/App.tsx b/evals/skia/19-rn-skia-runtime-effect-shader/app/App.tsx
new file mode 100644
index 0000000..630cffc
--- /dev/null
+++ b/evals/skia/19-rn-skia-runtime-effect-shader/app/App.tsx
@@ -0,0 +1,9 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/prompt.md b/evals/skia/19-rn-skia-runtime-effect-shader/prompt.md
new file mode 100644
index 0000000..6c3981b
--- /dev/null
+++ b/evals/skia/19-rn-skia-runtime-effect-shader/prompt.md
@@ -0,0 +1 @@
+Write a custom SKSL fragment shader compiled with Skia.RuntimeEffect.Make that generates a procedural color pattern based on UV coordinates. Pass at least one animated uniform value driven by a Reanimated shared value so the shader output changes over time.
diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/reference/App.tsx b/evals/skia/19-rn-skia-runtime-effect-shader/reference/App.tsx
new file mode 100644
index 0000000..f1f9a38
--- /dev/null
+++ b/evals/skia/19-rn-skia-runtime-effect-shader/reference/App.tsx
@@ -0,0 +1,41 @@
+import { useEffect } from 'react'
+import { useWindowDimensions } from 'react-native'
+import { Canvas, Fill, Shader, Skia, vec } from '@shopify/react-native-skia'
+import { useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
+
+const SKSL = `
+uniform float2 resolution;
+uniform float time;
+
+vec4 main(vec2 pos) {
+ vec2 uv = pos / resolution;
+ float r = 0.5 + 0.5 * sin(uv.x * 6.28 + time);
+ float g = 0.5 + 0.5 * sin(uv.y * 6.28 + time + 2.09);
+ float b = 0.5 + 0.5 * sin((uv.x + uv.y) * 3.14 + time + 4.19);
+ return vec4(r, g, b, 1.0);
+}
+`
+
+const source = Skia.RuntimeEffect.Make(SKSL)!
+
+export default function App() {
+ const { width, height } = useWindowDimensions()
+ const time = useSharedValue(0)
+
+ useEffect(() => {
+ time.value = withRepeat(withTiming(Math.PI * 2, { duration: 3000 }), -1, false)
+ }, [time])
+
+ const uniforms = useDerivedValue(() => ({
+ resolution: vec(width, height),
+ time: time.value,
+ }))
+
+ return (
+
+ )
+}
diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml
new file mode 100644
index 0000000..14d8f4c
--- /dev/null
+++ b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-runtime-effect-make
+ description: Must use Skia.RuntimeEffect.Make to compile a custom SKSL shader string.
+ - id: shader-used-as-fill-child
+ description: Must use the Shader component from @shopify/react-native-skia as a child of Fill or another drawing element.
+ - id: shader-receives-animated-uniform
+ description: Must pass at least one uniform to the Shader component that is driven by a Reanimated shared or derived value, making the output dynamic.
+ - id: sksl-uses-uv-or-position
+ description: The SKSL source must use the pos (or equivalent UV) parameter to produce a position-dependent color pattern.
diff --git a/evals/skia/20-rn-skia-canvas-snapshot/app/App.tsx b/evals/skia/20-rn-skia-canvas-snapshot/app/App.tsx
new file mode 100644
index 0000000..e0ca32b
--- /dev/null
+++ b/evals/skia/20-rn-skia-canvas-snapshot/app/App.tsx
@@ -0,0 +1,40 @@
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+export default function App() {
+ const handleSave = () => {}
+
+ return (
+
+
+
+
+ Save Snapshot
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0f172a',
+ },
+ canvas: {
+ flex: 1,
+ },
+ button: {
+ margin: 24,
+ paddingVertical: 14,
+ borderRadius: 10,
+ backgroundColor: '#3b82f6',
+ alignItems: 'center',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+})
diff --git a/evals/skia/20-rn-skia-canvas-snapshot/prompt.md b/evals/skia/20-rn-skia-canvas-snapshot/prompt.md
new file mode 100644
index 0000000..e0b5669
--- /dev/null
+++ b/evals/skia/20-rn-skia-canvas-snapshot/prompt.md
@@ -0,0 +1 @@
+Implement a Skia canvas with a drawing, and add a Save button that captures the canvas content as an image using makeImageSnapshot via a canvas ref. Call encodeToBytes on the result and log the byte length to confirm the snapshot was taken successfully.
diff --git a/evals/skia/20-rn-skia-canvas-snapshot/reference/App.tsx b/evals/skia/20-rn-skia-canvas-snapshot/reference/App.tsx
new file mode 100644
index 0000000..856bb96
--- /dev/null
+++ b/evals/skia/20-rn-skia-canvas-snapshot/reference/App.tsx
@@ -0,0 +1,53 @@
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import { Canvas, Circle, Fill, useCanvasRef } from '@shopify/react-native-skia'
+
+export default function App() {
+ const ref = useCanvasRef()
+
+ const handleSave = () => {
+ const image = ref.current?.makeImageSnapshot()
+ if (!image) {
+ console.warn('Snapshot failed: canvas ref not ready')
+ return
+ }
+ const bytes = image.encodeToBytes()
+ console.log('Snapshot byte length:', bytes.length)
+ }
+
+ return (
+
+
+
+
+ Save Snapshot
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0f172a',
+ },
+ canvas: {
+ flex: 1,
+ },
+ button: {
+ margin: 24,
+ paddingVertical: 14,
+ borderRadius: 10,
+ backgroundColor: '#3b82f6',
+ alignItems: 'center',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+})
diff --git a/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml
new file mode 100644
index 0000000..65bddff
--- /dev/null
+++ b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-canvas-ref
+ description: Must use useCanvasRef from @shopify/react-native-skia to obtain a ref to the Canvas component.
+ - id: calls-make-image-snapshot
+ description: Must call makeImageSnapshot() on the canvas ref object to capture the current canvas drawing as an SkImage.
+ - id: encodes-image-to-bytes
+ description: Must call encodeToBytes() or encodeToBase64() on the resulting SkImage to produce a usable data representation.
+ - id: snapshot-triggered-by-user-action
+ description: The snapshot must be triggered by a user action such as pressing a button, not automatically on mount.
diff --git a/evals/skia/21-rn-skia-sweep-gradient/app/App.tsx b/evals/skia/21-rn-skia-sweep-gradient/app/App.tsx
new file mode 100644
index 0000000..b5c6d0f
--- /dev/null
+++ b/evals/skia/21-rn-skia-sweep-gradient/app/App.tsx
@@ -0,0 +1,14 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const SIZE = 320
+const COLORS = ['#06b6d4', '#8b5cf6', '#f472b6', '#06b6d4']
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/21-rn-skia-sweep-gradient/prompt.md b/evals/skia/21-rn-skia-sweep-gradient/prompt.md
new file mode 100644
index 0000000..bb30996
--- /dev/null
+++ b/evals/skia/21-rn-skia-sweep-gradient/prompt.md
@@ -0,0 +1 @@
+Fill a circle on a Skia canvas with a SweepGradient shader that cycles through at least three distinct colors around the center point. The gradient center must match the circle center so the sweep reads as a radial color wheel or gauge segment.
diff --git a/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx b/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx
new file mode 100644
index 0000000..9d07e02
--- /dev/null
+++ b/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx
@@ -0,0 +1,26 @@
+import {
+ Canvas,
+ Circle,
+ Fill,
+ SweepGradient,
+ vec,
+} from '@shopify/react-native-skia'
+
+const SIZE = 320
+const CX = SIZE / 2
+const CY = SIZE / 2
+const R = 120
+const COLORS = ['#06b6d4', '#8b5cf6', '#f472b6', '#06b6d4']
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/21-rn-skia-sweep-gradient/requirements.yaml b/evals/skia/21-rn-skia-sweep-gradient/requirements.yaml
new file mode 100644
index 0000000..c1b655a
--- /dev/null
+++ b/evals/skia/21-rn-skia-sweep-gradient/requirements.yaml
@@ -0,0 +1,14 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-sweep-gradient-component
+ description: Must use the SweepGradient component from @shopify/react-native-skia as a child shader of a drawing element.
+ - id: sweep-has-multiple-colors
+ description: The SweepGradient colors array must include at least three distinct color stops to produce a visible angular color cycle.
+ - id: sweep-center-matches-shape-center
+ description: The SweepGradient c (center) coordinates must match the center of the shape it fills.
diff --git a/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx b/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx
new file mode 100644
index 0000000..315b62a
--- /dev/null
+++ b/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx
@@ -0,0 +1,15 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const SIZE = 320
+const CY = 160
+
+const COLOR1 = '#6366f1'
+const COLOR2 = '#ec4899'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/22-rn-skia-group-layer-effect/prompt.md b/evals/skia/22-rn-skia-group-layer-effect/prompt.md
new file mode 100644
index 0000000..377b81c
--- /dev/null
+++ b/evals/skia/22-rn-skia-group-layer-effect/prompt.md
@@ -0,0 +1 @@
+Draw at least two overlapping shapes inside a Skia Group and apply a blur to their combined composite using the Group layer prop. Pass a Paint element with a Blur child to layer — do not attach Blur directly to each shape individually.
diff --git a/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx b/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx
new file mode 100644
index 0000000..f61c8aa
--- /dev/null
+++ b/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx
@@ -0,0 +1,34 @@
+import {
+ Blur,
+ Canvas,
+ Circle,
+ Fill,
+ Group,
+ Paint,
+} from '@shopify/react-native-skia'
+
+const SIZE = 320
+const CY = 160
+
+const COLOR1 = '#6366f1'
+const COLOR2 = '#ec4899'
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/22-rn-skia-group-layer-effect/requirements.yaml b/evals/skia/22-rn-skia-group-layer-effect/requirements.yaml
new file mode 100644
index 0000000..6cd07e0
--- /dev/null
+++ b/evals/skia/22-rn-skia-group-layer-effect/requirements.yaml
@@ -0,0 +1,14 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-group-layer-prop
+ description: Must set the layer prop on a Group component from @shopify/react-native-skia.
+ - id: layer-uses-paint-with-blur
+ description: The layer prop must be a Paint element that contains a Blur image filter as a child.
+ - id: group-has-multiple-shapes
+ description: The Group with the layer effect must contain at least two drawing elements (e.g. circles or rects) so the effect applies to a composite, not a single shape.
diff --git a/evals/skia/23-rn-skia-paragraph-styled-text/app/App.tsx b/evals/skia/23-rn-skia-paragraph-styled-text/app/App.tsx
new file mode 100644
index 0000000..c41881c
--- /dev/null
+++ b/evals/skia/23-rn-skia-paragraph-styled-text/app/App.tsx
@@ -0,0 +1,11 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const PARAGRAPH_WIDTH = 300
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/23-rn-skia-paragraph-styled-text/prompt.md b/evals/skia/23-rn-skia-paragraph-styled-text/prompt.md
new file mode 100644
index 0000000..635a188
--- /dev/null
+++ b/evals/skia/23-rn-skia-paragraph-styled-text/prompt.md
@@ -0,0 +1 @@
+Render a multi-line text block on a Skia canvas using the Paragraph component and Skia.ParagraphBuilder. Apply at least two distinct text styles within the same paragraph (for example bold title plus regular subtitle) using pushStyle and pop, and center-align the paragraph with TextAlign.
diff --git a/evals/skia/23-rn-skia-paragraph-styled-text/reference/App.tsx b/evals/skia/23-rn-skia-paragraph-styled-text/reference/App.tsx
new file mode 100644
index 0000000..c1f1a5f
--- /dev/null
+++ b/evals/skia/23-rn-skia-paragraph-styled-text/reference/App.tsx
@@ -0,0 +1,47 @@
+import { useMemo } from 'react'
+import { useWindowDimensions } from 'react-native'
+import {
+ Canvas,
+ Fill,
+ FontStyle,
+ Paragraph,
+ Skia,
+ TextAlign,
+} from '@shopify/react-native-skia'
+
+const PARAGRAPH_WIDTH = 300
+
+export default function App() {
+ const { width } = useWindowDimensions()
+
+ const paragraph = useMemo(() => {
+ const para = Skia.ParagraphBuilder.Make({ textAlign: TextAlign.Center })
+ .pushStyle({
+ fontSize: 28,
+ color: Skia.Color('#f8fafc'),
+ fontStyle: FontStyle.Bold,
+ })
+ .addText('React Native Skia\n')
+ .pop()
+ .pushStyle({
+ fontSize: 16,
+ color: Skia.Color('#94a3b8'),
+ fontStyle: FontStyle.Normal,
+ })
+ .addText('Paragraph API with mixed styles')
+ .build()
+
+ para.layout(PARAGRAPH_WIDTH)
+ return para
+ }, [])
+
+ const x = (width - PARAGRAPH_WIDTH) / 2
+ const y = 120
+
+ return (
+
+ )
+}
diff --git a/evals/skia/23-rn-skia-paragraph-styled-text/requirements.yaml b/evals/skia/23-rn-skia-paragraph-styled-text/requirements.yaml
new file mode 100644
index 0000000..cfaaa31
--- /dev/null
+++ b/evals/skia/23-rn-skia-paragraph-styled-text/requirements.yaml
@@ -0,0 +1,16 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-paragraph-component
+ description: Must render text using the Paragraph component from @shopify/react-native-skia, not the low-level Text component alone.
+ - id: uses-paragraph-builder
+ description: Must build the paragraph with Skia.ParagraphBuilder.Make and chain addText calls.
+ - id: multiple-style-segments
+ description: Must apply at least two different text styles in the same paragraph using pushStyle and pop (e.g. different font sizes, weights, or colors).
+ - id: paragraph-uses-text-align
+ description: Must set a paragraph-level textAlign style such as TextAlign.Center on the ParagraphBuilder.Make options.
diff --git a/evals/skia/24-rn-skia-picture-save-restore/app/App.tsx b/evals/skia/24-rn-skia-picture-save-restore/app/App.tsx
new file mode 100644
index 0000000..0ce4074
--- /dev/null
+++ b/evals/skia/24-rn-skia-picture-save-restore/app/App.tsx
@@ -0,0 +1,19 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const SIZE = 320
+const BACKGROUND_COLOR = '#0f172a'
+const FIRST_RECT_COLOR = '#38bdf8'
+const AXIS_ALIGNED_RECT_COLOR = '#f472b6'
+const THIRD_RECT_COLOR = '#4ade80'
+const FIRST_RECT_ROTATION_DEG = 45
+const THIRD_RECT_ROTATION_DEG = -23
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/24-rn-skia-picture-save-restore/prompt.md b/evals/skia/24-rn-skia-picture-save-restore/prompt.md
new file mode 100644
index 0000000..455e49e
--- /dev/null
+++ b/evals/skia/24-rn-skia-picture-save-restore/prompt.md
@@ -0,0 +1 @@
+Record a Skia Picture with Skia.PictureRecorder and draw three rectangles on the imperative canvas: first rotated 45 degrees, then one axis-aligned at 0 degrees, then one rotated -23 degrees. Use the imperative canvas API to apply the transforms and draw the rectangles.
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
new file mode 100644
index 0000000..a856677
--- /dev/null
+++ b/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx
@@ -0,0 +1,57 @@
+import { useMemo } from 'react'
+import { Canvas, Fill, Picture, Skia } from '@shopify/react-native-skia'
+
+const SIZE = 320
+const CX = SIZE / 2
+const BACKGROUND_COLOR = '#0f172a'
+const FIRST_RECT_COLOR = '#38bdf8'
+const AXIS_ALIGNED_RECT_COLOR = '#f472b6'
+const THIRD_RECT_COLOR = '#4ade80'
+const FIRST_RECT_ROTATION_DEG = 45
+const THIRD_RECT_ROTATION_DEG = -23
+const RECT_WIDTH = 120
+const RECT_HEIGHT = 48
+
+const paint = Skia.Paint()
+paint.setAntiAlias(true)
+
+export default function App() {
+ const picture = useMemo(() => {
+ const recorder = Skia.PictureRecorder()
+ const canvas = recorder.beginRecording(Skia.XYWHRect(0, 0, SIZE, SIZE))
+
+ paint.setColor(Skia.Color(FIRST_RECT_COLOR))
+ canvas.save()
+ canvas.translate(CX, 90)
+ canvas.rotate(FIRST_RECT_ROTATION_DEG)
+ canvas.drawRect(
+ { x: -RECT_WIDTH / 2, y: -RECT_HEIGHT / 2, width: RECT_WIDTH, height: RECT_HEIGHT },
+ paint
+ )
+ canvas.restore()
+
+ paint.setColor(Skia.Color(AXIS_ALIGNED_RECT_COLOR))
+ canvas.drawRect({ x: 40, y: 144, width: 240, height: 32 }, paint)
+
+ paint.setColor(Skia.Color(THIRD_RECT_COLOR))
+ canvas.save()
+ canvas.translate(CX, 230)
+ canvas.rotate(THIRD_RECT_ROTATION_DEG)
+ canvas.drawRect(
+ { x: -RECT_WIDTH / 2, y: -RECT_HEIGHT / 2, width: RECT_WIDTH, height: RECT_HEIGHT },
+ paint
+ )
+ canvas.restore()
+
+ return recorder.finishRecordingAsPicture()
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/evals/skia/24-rn-skia-picture-save-restore/requirements.yaml b/evals/skia/24-rn-skia-picture-save-restore/requirements.yaml
new file mode 100644
index 0000000..302e348
--- /dev/null
+++ b/evals/skia/24-rn-skia-picture-save-restore/requirements.yaml
@@ -0,0 +1,20 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-picture-recorder
+ description: Must create the drawing with Skia.PictureRecorder using beginRecording and finishRecordingAsPicture.
+ - id: uses-picture-component
+ description: Must render the recorded picture with the Picture component from @shopify/react-native-skia.
+ - id: uses-canvas-save-and-restore
+ description: Must call canvas.save() before each rotated draw and canvas.restore() after it, as paired stack operations.
+ - id: first-rect-rotated-45-degrees
+ description: The first rectangle must be drawn inside a save/restore block with a 45 degree rotation applied before the draw call.
+ - id: second-rect-axis-aligned
+ description: The second rectangle must be drawn without an active rotation transform — axis-aligned at 0 degrees between the two rotated rectangles.
+ - id: third-rect-rotated-minus-23-degrees
+ description: The third rectangle must be drawn inside a save/restore block with a -23 degree rotation applied before the draw call.
diff --git a/evals/skia/25-rn-skia-lottie-playback/app/App.tsx b/evals/skia/25-rn-skia-lottie-playback/app/App.tsx
new file mode 100644
index 0000000..c1c5102
--- /dev/null
+++ b/evals/skia/25-rn-skia-lottie-playback/app/App.tsx
@@ -0,0 +1,33 @@
+import { Canvas, Fill } from '@shopify/react-native-skia'
+
+const CANVAS_SIZE = 300
+const BACKGROUND_COLOR = '#0f172a'
+
+export const LOTTIE_JSON = {
+ v: '5.5.7',
+ fr: 30,
+ ip: 0,
+ op: 90,
+ w: 300,
+ h: 300,
+ nm: 'Pulsing Circle',
+ ddd: 0,
+ /**
+ * Assume this asset variable is complete.
+ */
+}
+
+export default function App() {
+ return (
+
+ )
+}
diff --git a/evals/skia/25-rn-skia-lottie-playback/prompt.md b/evals/skia/25-rn-skia-lottie-playback/prompt.md
new file mode 100644
index 0000000..5e13738
--- /dev/null
+++ b/evals/skia/25-rn-skia-lottie-playback/prompt.md
@@ -0,0 +1 @@
+Load the provided Lottie JSON with Skia.Skottie.Make(JSON.stringify(...)) and render it on a Skia canvas using the Skottie component. Drive looping playback by deriving the current frame from useClock with useDerivedValue, using the animation fps() and duration() to wrap the frame index. Assume the given Lottie JSON resource variable (LOTTIE_JSON) is complete.
diff --git a/evals/skia/25-rn-skia-lottie-playback/reference/App.tsx b/evals/skia/25-rn-skia-lottie-playback/reference/App.tsx
new file mode 100644
index 0000000..3db46a6
--- /dev/null
+++ b/evals/skia/25-rn-skia-lottie-playback/reference/App.tsx
@@ -0,0 +1,58 @@
+import { useMemo } from 'react'
+import {
+ Canvas,
+ Fill,
+ Skia,
+ Skottie,
+ useClock,
+} from '@shopify/react-native-skia'
+import { useDerivedValue } from 'react-native-reanimated'
+
+const CANVAS_SIZE = 300
+const BACKGROUND_COLOR = '#0f172a'
+
+const LOTTIE_JSON = {
+ v: '5.5.7',
+ fr: 30,
+ ip: 0,
+ op: 90,
+ w: 300,
+ h: 300,
+ nm: 'Pulsing Circle',
+ ddd: 0,
+ /**
+ * Assume this asset variable is complete.
+ */
+}
+
+export default function App() {
+ const animation = useMemo(
+ () => Skia.Skottie.Make(JSON.stringify(LOTTIE_JSON)),
+ []
+ )
+
+ const clock = useClock()
+ const frame = useDerivedValue(() => {
+ if (!animation) {
+ return 0
+ }
+
+ const fps = animation.fps()
+ const duration = animation.duration()
+ return Math.floor((clock.value / 1000) * fps) % Math.floor(duration * fps)
+ })
+
+ return (
+
+ )
+}
diff --git a/evals/skia/25-rn-skia-lottie-playback/requirements.yaml b/evals/skia/25-rn-skia-lottie-playback/requirements.yaml
new file mode 100644
index 0000000..99b26ff
--- /dev/null
+++ b/evals/skia/25-rn-skia-lottie-playback/requirements.yaml
@@ -0,0 +1,18 @@
+version: 1
+inputs:
+ files:
+ - app/App.tsx
+requirements:
+ - id: uses-canvas-component
+ description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia.
+ weight: 0.1
+ - id: uses-skottie-make
+ description: Must create the animation with Skia.Skottie.Make, passing the Lottie JSON via JSON.stringify.
+ - id: uses-skottie-component
+ description: Must render the Skottie component from @shopify/react-native-skia with the created animation and a frame prop.
+ - id: uses-use-clock
+ description: Must use the useClock hook from @shopify/react-native-skia to drive playback timing.
+ - id: uses-derived-value-for-frame
+ description: Must compute the Skottie frame prop with useDerivedValue from react-native-reanimated, reading the clock shared value.
+ - id: frame-loops-with-duration-and-fps
+ description: Must wrap the derived frame index with modulo using the animation fps() and duration() so playback loops continuously.
diff --git a/evals/skia/README.md b/evals/skia/README.md
new file mode 100644
index 0000000..3b18ea2
--- /dev/null
+++ b/evals/skia/README.md
@@ -0,0 +1,100 @@
+# Skia eval category
+
+React Native Skia evals — testing how well LLMs implement high-performance 2D drawing using `@shopify/react-native-skia`.
+
+## Official docs
+
+- Canvas overview: https://shopify.github.io/react-native-skia/docs/canvas/overview
+- Painting: https://shopify.github.io/react-native-skia/docs/paint/overview
+- Shapes: https://shopify.github.io/react-native-skia/docs/shapes/rect
+- Path: https://shopify.github.io/react-native-skia/docs/shapes/path
+- Text: https://shopify.github.io/react-native-skia/docs/text/text
+- Paragraph: https://shopify.github.io/react-native-skia/docs/text/paragraph
+- Group: https://shopify.github.io/react-native-skia/docs/group
+- Images: https://shopify.github.io/react-native-skia/docs/images
+- Image filters: https://shopify.github.io/react-native-skia/docs/image-filters/overview
+- Shaders: https://shopify.github.io/react-native-skia/docs/shaders/overview
+- Gradients: https://shopify.github.io/react-native-skia/docs/shaders/gradients
+- Pictures: https://shopify.github.io/react-native-skia/docs/shapes/pictures
+- Mask: https://shopify.github.io/react-native-skia/docs/mask
+- Animations: https://shopify.github.io/react-native-skia/docs/animations/animations
+- Gestures: https://shopify.github.io/react-native-skia/docs/animations/gestures
+- Skottie: https://shopify.github.io/react-native-skia/docs/skottie
+
+## Best-practice inventory
+
+| # | Rule | Source |
+| --- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
+| 1 | Render all Skia drawing inside a `