From 8a51ff03a1d312ba3db949f4317a6e27a438f801 Mon Sep 17 00:00:00 2001 From: andriicallstack Date: Tue, 12 May 2026 13:25:03 +0200 Subject: [PATCH 01/14] feat(evals): add skia category with 20 react-native-skia evals --- .../prompt.md | 1 + .../reference/App.tsx | 35 ++++++++++ .../requirements.yaml | 10 +++ .../02-rn-skia-shape-primitives/prompt.md | 1 + .../reference/App.tsx | 43 ++++++++++++ .../requirements.yaml | 12 ++++ evals/skia/03-rn-skia-path-drawing/prompt.md | 1 + .../03-rn-skia-path-drawing/reference/App.tsx | 28 ++++++++ .../03-rn-skia-path-drawing/requirements.yaml | 10 +++ .../04-rn-skia-paint-stroke-fill/prompt.md | 1 + .../reference/App.tsx | 28 ++++++++ .../requirements.yaml | 8 +++ .../skia/05-rn-skia-linear-gradient/prompt.md | 1 + .../reference/App.tsx | 39 +++++++++++ .../requirements.yaml | 10 +++ .../skia/06-rn-skia-radial-gradient/prompt.md | 1 + .../reference/App.tsx | 21 ++++++ .../requirements.yaml | 8 +++ evals/skia/07-rn-skia-image-display/prompt.md | 1 + .../reference/App.tsx | 47 +++++++++++++ .../requirements.yaml | 10 +++ .../skia/08-rn-skia-text-rendering/prompt.md | 1 + .../reference/App.tsx | 32 +++++++++ .../requirements.yaml | 10 +++ evals/skia/09-rn-skia-blur-filter/prompt.md | 1 + .../09-rn-skia-blur-filter/reference/App.tsx | 54 +++++++++++++++ .../09-rn-skia-blur-filter/requirements.yaml | 8 +++ .../10-rn-skia-color-matrix-filter/prompt.md | 1 + .../reference/App.tsx | 57 +++++++++++++++ .../requirements.yaml | 10 +++ .../prompt.md | 1 + .../reference/App.tsx | 27 ++++++++ .../requirements.yaml | 10 +++ .../prompt.md | 1 + .../reference/App.tsx | 27 ++++++++ .../requirements.yaml | 10 +++ .../prompt.md | 1 + .../reference/App.tsx | 29 ++++++++ .../requirements.yaml | 10 +++ evals/skia/14-rn-skia-gesture-pan/prompt.md | 1 + .../14-rn-skia-gesture-pan/reference/App.tsx | 31 +++++++++ .../14-rn-skia-gesture-pan/requirements.yaml | 10 +++ evals/skia/15-rn-skia-transforms/prompt.md | 1 + .../15-rn-skia-transforms/reference/App.tsx | 57 +++++++++++++++ .../15-rn-skia-transforms/requirements.yaml | 10 +++ .../16-rn-skia-clip-rect-and-path/prompt.md | 1 + .../reference/App.tsx | 30 ++++++++ .../requirements.yaml | 10 +++ evals/skia/17-rn-skia-blend-mode/prompt.md | 1 + .../17-rn-skia-blend-mode/reference/App.tsx | 21 ++++++ .../17-rn-skia-blend-mode/requirements.yaml | 10 +++ .../18-rn-skia-svg-path-rendering/prompt.md | 1 + .../reference/App.tsx | 30 ++++++++ .../requirements.yaml | 10 +++ .../prompt.md | 1 + .../reference/App.tsx | 41 +++++++++++ .../requirements.yaml | 10 +++ .../skia/20-rn-skia-canvas-snapshot/prompt.md | 1 + .../reference/App.tsx | 53 ++++++++++++++ .../requirements.yaml | 10 +++ evals/skia/README.md | 69 +++++++++++++++++++ runner/evaluators/llm/judge-client.ts | 7 +- runner/solver/index.ts | 9 ++- runner/utils/opencode.ts | 35 +++++++++- 64 files changed, 1059 insertions(+), 7 deletions(-) create mode 100644 evals/skia/01-rn-skia-canvas-fill-background/prompt.md create mode 100644 evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx create mode 100644 evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml create mode 100644 evals/skia/02-rn-skia-shape-primitives/prompt.md create mode 100644 evals/skia/02-rn-skia-shape-primitives/reference/App.tsx create mode 100644 evals/skia/02-rn-skia-shape-primitives/requirements.yaml create mode 100644 evals/skia/03-rn-skia-path-drawing/prompt.md create mode 100644 evals/skia/03-rn-skia-path-drawing/reference/App.tsx create mode 100644 evals/skia/03-rn-skia-path-drawing/requirements.yaml create mode 100644 evals/skia/04-rn-skia-paint-stroke-fill/prompt.md create mode 100644 evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx create mode 100644 evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml create mode 100644 evals/skia/05-rn-skia-linear-gradient/prompt.md create mode 100644 evals/skia/05-rn-skia-linear-gradient/reference/App.tsx create mode 100644 evals/skia/05-rn-skia-linear-gradient/requirements.yaml create mode 100644 evals/skia/06-rn-skia-radial-gradient/prompt.md create mode 100644 evals/skia/06-rn-skia-radial-gradient/reference/App.tsx create mode 100644 evals/skia/06-rn-skia-radial-gradient/requirements.yaml create mode 100644 evals/skia/07-rn-skia-image-display/prompt.md create mode 100644 evals/skia/07-rn-skia-image-display/reference/App.tsx create mode 100644 evals/skia/07-rn-skia-image-display/requirements.yaml create mode 100644 evals/skia/08-rn-skia-text-rendering/prompt.md create mode 100644 evals/skia/08-rn-skia-text-rendering/reference/App.tsx create mode 100644 evals/skia/08-rn-skia-text-rendering/requirements.yaml create mode 100644 evals/skia/09-rn-skia-blur-filter/prompt.md create mode 100644 evals/skia/09-rn-skia-blur-filter/reference/App.tsx create mode 100644 evals/skia/09-rn-skia-blur-filter/requirements.yaml create mode 100644 evals/skia/10-rn-skia-color-matrix-filter/prompt.md create mode 100644 evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx create mode 100644 evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml create mode 100644 evals/skia/11-rn-skia-reanimated-basic-animation/prompt.md create mode 100644 evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx create mode 100644 evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml create mode 100644 evals/skia/12-rn-skia-derived-value-animation/prompt.md create mode 100644 evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx create mode 100644 evals/skia/12-rn-skia-derived-value-animation/requirements.yaml create mode 100644 evals/skia/13-rn-skia-animated-color-interpolation/prompt.md create mode 100644 evals/skia/13-rn-skia-animated-color-interpolation/reference/App.tsx create mode 100644 evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml create mode 100644 evals/skia/14-rn-skia-gesture-pan/prompt.md create mode 100644 evals/skia/14-rn-skia-gesture-pan/reference/App.tsx create mode 100644 evals/skia/14-rn-skia-gesture-pan/requirements.yaml create mode 100644 evals/skia/15-rn-skia-transforms/prompt.md create mode 100644 evals/skia/15-rn-skia-transforms/reference/App.tsx create mode 100644 evals/skia/15-rn-skia-transforms/requirements.yaml create mode 100644 evals/skia/16-rn-skia-clip-rect-and-path/prompt.md create mode 100644 evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx create mode 100644 evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml create mode 100644 evals/skia/17-rn-skia-blend-mode/prompt.md create mode 100644 evals/skia/17-rn-skia-blend-mode/reference/App.tsx create mode 100644 evals/skia/17-rn-skia-blend-mode/requirements.yaml create mode 100644 evals/skia/18-rn-skia-svg-path-rendering/prompt.md create mode 100644 evals/skia/18-rn-skia-svg-path-rendering/reference/App.tsx create mode 100644 evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml create mode 100644 evals/skia/19-rn-skia-runtime-effect-shader/prompt.md create mode 100644 evals/skia/19-rn-skia-runtime-effect-shader/reference/App.tsx create mode 100644 evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml create mode 100644 evals/skia/20-rn-skia-canvas-snapshot/prompt.md create mode 100644 evals/skia/20-rn-skia-canvas-snapshot/reference/App.tsx create mode 100644 evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml create mode 100644 evals/skia/README.md 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 00000000..96c1df7a --- /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 00000000..3bd2bed0 --- /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 00000000..bc71193a --- /dev/null +++ b/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +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/prompt.md b/evals/skia/02-rn-skia-shape-primitives/prompt.md new file mode 100644 index 00000000..70ab3a8f --- /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 00000000..bb780031 --- /dev/null +++ b/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx @@ -0,0 +1,43 @@ +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 00000000..c9e486a5 --- /dev/null +++ b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml @@ -0,0 +1,12 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/03-rn-skia-path-drawing/prompt.md new file mode 100644 index 00000000..3722baf9 --- /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 00000000..b5bcf82a --- /dev/null +++ b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx @@ -0,0 +1,28 @@ +import { useMemo } from 'react' +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 00000000..12f2e51a --- /dev/null +++ b/evals/skia/03-rn-skia-path-drawing/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/04-rn-skia-paint-stroke-fill/prompt.md new file mode 100644 index 00000000..100fd41a --- /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 00000000..2d86b9a3 --- /dev/null +++ b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx @@ -0,0 +1,28 @@ +import { Canvas, Circle, Fill, Group, Paint, Rect, vec } 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 00000000..202a2cce --- /dev/null +++ b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml @@ -0,0 +1,8 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/05-rn-skia-linear-gradient/prompt.md new file mode 100644 index 00000000..a784ca28 --- /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 00000000..9c479475 --- /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 00000000..577d0200 --- /dev/null +++ b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/06-rn-skia-radial-gradient/prompt.md new file mode 100644 index 00000000..1072ef84 --- /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 00000000..970867d7 --- /dev/null +++ b/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx @@ -0,0 +1,21 @@ +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 00000000..14581a34 --- /dev/null +++ b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml @@ -0,0 +1,8 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/07-rn-skia-image-display/prompt.md new file mode 100644 index 00000000..c370421a --- /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 00000000..28c471d9 --- /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 ( + + + {image ? ( + + ) : ( + + )} + + + ) +} + +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 00000000..c19ad633 --- /dev/null +++ b/evals/skia/07-rn-skia-image-display/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/08-rn-skia-text-rendering/prompt.md new file mode 100644 index 00000000..e59d09cf --- /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 00000000..1b25f8f1 --- /dev/null +++ b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx @@ -0,0 +1,32 @@ +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 00000000..0c75fe32 --- /dev/null +++ b/evals/skia/08-rn-skia-text-rendering/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/09-rn-skia-blur-filter/prompt.md new file mode 100644 index 00000000..442d92e7 --- /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 00000000..86cda9c8 --- /dev/null +++ b/evals/skia/09-rn-skia-blur-filter/reference/App.tsx @@ -0,0 +1,54 @@ +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 > 0 && } + + + + + + 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 00000000..77b769a6 --- /dev/null +++ b/evals/skia/09-rn-skia-blur-filter/requirements.yaml @@ -0,0 +1,8 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/10-rn-skia-color-matrix-filter/prompt.md new file mode 100644 index 00000000..cbabf52e --- /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 00000000..985d09cb --- /dev/null +++ b/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx @@ -0,0 +1,57 @@ +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 ( + + + + + + {filtered && } + + + + + + + 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 00000000..ed8c2926 --- /dev/null +++ b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/11-rn-skia-reanimated-basic-animation/prompt.md new file mode 100644 index 00000000..732da245 --- /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 00000000..2df691f8 --- /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 00000000..e781656f --- /dev/null +++ b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/12-rn-skia-derived-value-animation/prompt.md new file mode 100644 index 00000000..3a86f7bd --- /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 00000000..dae0f174 --- /dev/null +++ b/evals/skia/12-rn-skia-derived-value-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 { 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 00000000..c4ba4216 --- /dev/null +++ b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/13-rn-skia-animated-color-interpolation/prompt.md new file mode 100644 index 00000000..d65e8210 --- /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 00000000..f5c128e5 --- /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 00000000..77aa5a28 --- /dev/null +++ b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/14-rn-skia-gesture-pan/prompt.md new file mode 100644 index 00000000..1d57f004 --- /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 00000000..d3025a3b --- /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 00000000..a3243791 --- /dev/null +++ b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/15-rn-skia-transforms/prompt.md new file mode 100644 index 00000000..a73120fe --- /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 00000000..c4fcf6b0 --- /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 00000000..77177589 --- /dev/null +++ b/evals/skia/15-rn-skia-transforms/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md new file mode 100644 index 00000000..b87d2b22 --- /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 00000000..29b51c73 --- /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 ( + + + + {/* ClipRect: restrict a gradient to a rectangle */} + + + + + + + + {/* ClipPath: clip a solid block to a star shape */} + + + + + + ) +} 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 00000000..22406b72 --- /dev/null +++ b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/17-rn-skia-blend-mode/prompt.md new file mode 100644 index 00000000..85df0ff9 --- /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 00000000..8cbc93b0 --- /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 00000000..23728f0e --- /dev/null +++ b/evals/skia/17-rn-skia-blend-mode/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/18-rn-skia-svg-path-rendering/prompt.md new file mode 100644 index 00000000..696a94ef --- /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 00000000..80a3b640 --- /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 00000000..4c3de651 --- /dev/null +++ b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/19-rn-skia-runtime-effect-shader/prompt.md new file mode 100644 index 00000000..6c3981b8 --- /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 00000000..f1f9a38b --- /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 00000000..90487526 --- /dev/null +++ b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/prompt.md b/evals/skia/20-rn-skia-canvas-snapshot/prompt.md new file mode 100644 index 00000000..e0b56696 --- /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 00000000..856bb96d --- /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 00000000..b9497b4b --- /dev/null +++ b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml @@ -0,0 +1,10 @@ +version: 1 +requirements: + - 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/README.md b/evals/skia/README.md new file mode 100644 index 00000000..3f2d1813 --- /dev/null +++ b/evals/skia/README.md @@ -0,0 +1,69 @@ +# 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 +- 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 +- Animations: https://shopify.github.io/react-native-skia/docs/animations/animations +- Gestures: https://shopify.github.io/react-native-skia/docs/animations/gestures + +## Best-practice inventory + +| # | Rule | Source | +|---|------|--------| +| 1 | Render all Skia drawing inside a `` root component | canvas/overview | +| 2 | Pass Reanimated `useSharedValue` / `useDerivedValue` directly as Skia props — no `createAnimatedComponent` or `useAnimatedProps` needed | animations/animations | +| 3 | Use `interpolateColors` from `@shopify/react-native-skia`, not `interpolateColor` from Reanimated, for color transitions | animations/animations | +| 4 | Wrap gesture handlers around the `` using `GestureDetector`; for per-element gesture tracking, overlay an `Animated.View` | animations/gestures | +| 5 | Use `useCanvasSize` (JS thread) or the `onSize` shared value prop (UI thread) to read canvas dimensions reactively | canvas/overview | +| 6 | Build paths imperatively with `Skia.Path.Make()` or parse SVG strings with `Skia.Path.MakeFromSVGString` | shapes/path | +| 7 | Use `Paint` children on drawing elements for multiple fills/strokes; inherit paint attributes via `Group` | paint/overview | +| 8 | Compose image filters by nesting `Blur`, `ColorMatrix`, etc. as children of the target drawing element or `Group` | image-filters/overview | +| 9 | Compile custom SKSL shaders with `Skia.RuntimeEffect.Make`; use the `Shader` component as a child of `Fill` | shaders/overview | +| 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 | + +## Eval traceability + +| Eval | Primary best-practice rule(s) | +|------|-------------------------------| +| 01 – canvas-fill-background | 1, 5 | +| 02 – shape-primitives | 1 | +| 03 – path-drawing | 6 | +| 04 – paint-stroke-fill | 7 | +| 05 – linear-gradient | 1, 2 | +| 06 – radial-gradient | 1, 2 | +| 07 – image-display | 1 | +| 08 – text-rendering | 11 | +| 09 – blur-filter | 8 | +| 10 – color-matrix-filter | 8 | +| 11 – reanimated-basic-animation | 2 | +| 12 – derived-value-animation | 2 | +| 13 – animated-color-interpolation | 3 | +| 14 – gesture-pan-interaction | 4 | +| 15 – transforms | 2 | +| 16 – clip-rect-and-path | 13 | +| 17 – blend-mode | 12 | +| 18 – svg-path-rendering | 6 | +| 19 – runtime-effect-shader | 9 | +| 20 – canvas-snapshot | 10 | + +## Common issue clusters + +| Tag | Examples | +|-----|---------| +| API misuse | Using `interpolateColor` from Reanimated instead of `interpolateColors` from Skia; using `createAnimatedComponent` unnecessarily | +| Setup | Forgetting `` wrapper; rendering RN `` instead of Skia `` | +| Performance | Per-render path construction instead of memoization; creating `SkPaint` objects inside render | +| Platform parity | `y` origin for `Text` is baseline not top-left (differs from RN Text) | +| Edge case | Not handling `useImage` null state before image is loaded | diff --git a/runner/evaluators/llm/judge-client.ts b/runner/evaluators/llm/judge-client.ts index 5c117acd..f0c8cbb7 100644 --- a/runner/evaluators/llm/judge-client.ts +++ b/runner/evaluators/llm/judge-client.ts @@ -1,4 +1,4 @@ -import { Output, generateText } from 'ai' +import { Output, extractJsonMiddleware, generateText, wrapLanguageModel } from 'ai' import { createOpencode } from 'ai-sdk-provider-opencode-sdk' import { z } from 'zod' import { ensureOpencodeServerStarted } from 'runner/utils/opencode' @@ -35,7 +35,10 @@ export async function runJudgeCall( }) const response = await generateText({ - model: provider(model, { createNewSession: true }), + model: wrapLanguageModel({ + model: provider(model, { createNewSession: true }), + middleware: extractJsonMiddleware(), + }), prompt, abortSignal: AbortSignal.timeout(timeout), output: Output.object({ diff --git a/runner/solver/index.ts b/runner/solver/index.ts index 7d123f51..a039651b 100644 --- a/runner/solver/index.ts +++ b/runner/solver/index.ts @@ -1,4 +1,4 @@ -import { Output, generateText } from 'ai' +import { Output, extractJsonMiddleware, generateText, wrapLanguageModel } from 'ai' import { createOpencode } from 'ai-sdk-provider-opencode-sdk' import { mkdir, writeFile } from 'node:fs/promises' import path from 'node:path' @@ -103,14 +103,17 @@ export async function runSolver(params: { const prompt = buildSolverPrompt(params.prompt, params.files) const { output } = await generateText({ - model: provider(params.model, { createNewSession: true }), + model: wrapLanguageModel({ + model: provider(params.model, { createNewSession: true }), + middleware: extractJsonMiddleware(), + }), prompt, system: SYSTEM_PROMPT, abortSignal: AbortSignal.timeout(params.timeout), output: Output.object({ schema: solverOutputSchema, description: 'Generated files that satisfy the task', - }), + }), }) return output diff --git a/runner/utils/opencode.ts b/runner/utils/opencode.ts index c97a764f..ae4a8311 100644 --- a/runner/utils/opencode.ts +++ b/runner/utils/opencode.ts @@ -2,21 +2,52 @@ import { createOpencodeServer } from '@opencode-ai/sdk/v2/server' let serverPromise: Promise | undefined +const DEFAULT_PORT = 4096 + +async function isServerAlive(port: number): Promise { + const base = `http://127.0.0.1:${port}` + try { + const res = await fetch(`${base}/global/health`, { + signal: AbortSignal.timeout(2000), + }) + return res.ok + } catch { + return false + } +} + +function buildProviderConfig() { + const anthropicKey = process.env.ANTHROPIC_API_KEY + if (!anthropicKey) { + return undefined + } + return { + provider: { + anthropic: { apiKey: anthropicKey }, + }, + } +} + /* Starts one reusable OpenCode server process for solver and judge stages. + If a server is already listening on the target port it is reused as-is. */ export async function ensureOpencodeServerStarted({ - port, - timeout = 120000 + port = DEFAULT_PORT, + timeout = 120000, }: { port?: number, timeout?: number, }) { if (!serverPromise) { serverPromise = (async () => { + if (await isServerAlive(port)) { + return + } await createOpencodeServer({ port, timeout, + config: buildProviderConfig(), }) })() } From c24deb4a2e4988ab00728ae5ab890b33a9b20fe7 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 15:30:11 +0200 Subject: [PATCH 02/14] feat: default opencode port value, do not run opencode server if already up, deintegrate provider-specific code --- runner/config.ts | 4 ++-- runner/evaluators/llm/judge-client.ts | 2 +- runner/solver/index.ts | 2 +- runner/utils/opencode.ts | 23 ++++------------------- 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/runner/config.ts b/runner/config.ts index 76efecf9..34ce93e0 100644 --- a/runner/config.ts +++ b/runner/config.ts @@ -23,7 +23,7 @@ export function parseCliArgs(argv: string[] = Bun.argv.slice(2)) { 'solver-model': { type: 'string' }, 'pattern': { type: 'string', default: 'evals/**/*' }, 'timeout': { type: 'string', default: '120000' }, - 'port': { type: 'string' }, + 'port': { type: 'string', default: '4096' }, }, strict: true, allowPositionals: false, @@ -37,7 +37,7 @@ export function parseCliArgs(argv: string[] = Bun.argv.slice(2)) { solverModel: values['solver-model'], pattern: values.pattern, timeout: parsePositiveInteger(values.timeout, '--timeout'), - port: values.port ? parsePositiveInteger(values.port, '--port') : undefined, + port: parsePositiveInteger(values.port, '--port'), } } diff --git a/runner/evaluators/llm/judge-client.ts b/runner/evaluators/llm/judge-client.ts index f0c8cbb7..d88d2f93 100644 --- a/runner/evaluators/llm/judge-client.ts +++ b/runner/evaluators/llm/judge-client.ts @@ -25,7 +25,7 @@ export async function runJudgeCall( prompt: string, model: string, timeout: number, - port?: number + port: number ) { await ensureOpencodeServerStarted({ timeout, port }) diff --git a/runner/solver/index.ts b/runner/solver/index.ts index a039651b..96c7e0da 100644 --- a/runner/solver/index.ts +++ b/runner/solver/index.ts @@ -91,7 +91,7 @@ export async function runSolver(params: { files: LoadedFile[], model: string timeout: number - port?: number + port: number }) { await ensureOpencodeServerStarted(params) diff --git a/runner/utils/opencode.ts b/runner/utils/opencode.ts index ae4a8311..f4ec8aed 100644 --- a/runner/utils/opencode.ts +++ b/runner/utils/opencode.ts @@ -2,13 +2,11 @@ import { createOpencodeServer } from '@opencode-ai/sdk/v2/server' let serverPromise: Promise | undefined -const DEFAULT_PORT = 4096 - async function isServerAlive(port: number): Promise { const base = `http://127.0.0.1:${port}` try { const res = await fetch(`${base}/global/health`, { - signal: AbortSignal.timeout(2000), + signal: AbortSignal.timeout(1500), }) return res.ok } catch { @@ -16,28 +14,16 @@ async function isServerAlive(port: number): Promise { } } -function buildProviderConfig() { - const anthropicKey = process.env.ANTHROPIC_API_KEY - if (!anthropicKey) { - return undefined - } - return { - provider: { - anthropic: { apiKey: anthropicKey }, - }, - } -} - /* Starts one reusable OpenCode server process for solver and judge stages. If a server is already listening on the target port it is reused as-is. */ export async function ensureOpencodeServerStarted({ - port = DEFAULT_PORT, + port, timeout = 120000, }: { - port?: number, - timeout?: number, + port: number + timeout?: number }) { if (!serverPromise) { serverPromise = (async () => { @@ -47,7 +33,6 @@ export async function ensureOpencodeServerStarted({ await createOpencodeServer({ port, timeout, - config: buildProviderConfig(), }) })() } From fa7f09d35dbe2a7101c2203c5fa0b2a3968c76f9 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 15:34:28 +0200 Subject: [PATCH 03/14] chore: changes after CR --- .../03-rn-skia-path-drawing/reference/App.tsx | 1 - .../reference/App.tsx | 2 +- evals/skia/README.md | 88 +++++++++---------- 3 files changed, 45 insertions(+), 46 deletions(-) diff --git a/evals/skia/03-rn-skia-path-drawing/reference/App.tsx b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx index b5bcf82a..2c737b89 100644 --- a/evals/skia/03-rn-skia-path-drawing/reference/App.tsx +++ b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx @@ -1,4 +1,3 @@ -import { useMemo } from 'react' import { Canvas, Fill, Path, Skia } from '@shopify/react-native-skia' const STROKE_WIDTH = 4 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 index 2d86b9a3..48fe0ae6 100644 --- a/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx +++ b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx @@ -1,4 +1,4 @@ -import { Canvas, Circle, Fill, Group, Paint, Rect, vec } from '@shopify/react-native-skia' +import { Canvas, Circle, Fill, Group, Paint, Rect } from '@shopify/react-native-skia' const SIZE = 300 const CX = SIZE / 2 diff --git a/evals/skia/README.md b/evals/skia/README.md index 3f2d1813..c5ffb30b 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -17,53 +17,53 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D ## Best-practice inventory -| # | Rule | Source | -|---|------|--------| -| 1 | Render all Skia drawing inside a `` root component | canvas/overview | -| 2 | Pass Reanimated `useSharedValue` / `useDerivedValue` directly as Skia props — no `createAnimatedComponent` or `useAnimatedProps` needed | animations/animations | -| 3 | Use `interpolateColors` from `@shopify/react-native-skia`, not `interpolateColor` from Reanimated, for color transitions | animations/animations | -| 4 | Wrap gesture handlers around the `` using `GestureDetector`; for per-element gesture tracking, overlay an `Animated.View` | animations/gestures | -| 5 | Use `useCanvasSize` (JS thread) or the `onSize` shared value prop (UI thread) to read canvas dimensions reactively | canvas/overview | -| 6 | Build paths imperatively with `Skia.Path.Make()` or parse SVG strings with `Skia.Path.MakeFromSVGString` | shapes/path | -| 7 | Use `Paint` children on drawing elements for multiple fills/strokes; inherit paint attributes via `Group` | paint/overview | -| 8 | Compose image filters by nesting `Blur`, `ColorMatrix`, etc. as children of the target drawing element or `Group` | image-filters/overview | -| 9 | Compile custom SKSL shaders with `Skia.RuntimeEffect.Make`; use the `Shader` component as a child of `Fill` | shaders/overview | -| 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 | +| # | Rule | Source | +| --- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| 1 | Render all Skia drawing inside a `` root component | canvas/overview | +| 2 | Pass Reanimated `useSharedValue` / `useDerivedValue` directly as Skia props — no `createAnimatedComponent` or `useAnimatedProps` needed | animations/animations | +| 3 | Use `interpolateColors` from `@shopify/react-native-skia`, not `interpolateColor` from Reanimated, for color transitions | animations/animations | +| 4 | Wrap gesture handlers around the `` using `GestureDetector`; for per-element gesture tracking, overlay an `Animated.View` | animations/gestures | +| 5 | Use `useCanvasSize` (JS thread) or the `onSize` shared value prop (UI thread) to read canvas dimensions reactively | canvas/overview | +| 6 | Build paths imperatively with `Skia.Path.Make()` or parse SVG strings with `Skia.Path.MakeFromSVGString` | shapes/path | +| 7 | Use `Paint` children on drawing elements for multiple fills/strokes; inherit paint attributes via `Group` | paint/overview | +| 8 | Compose image filters by nesting `Blur`, `ColorMatrix`, etc. as children of the target drawing element or `Group` | image-filters/overview | +| 9 | Compile custom SKSL shaders with `Skia.RuntimeEffect.Make`; use the `Shader` component as a child of `Fill` | shaders/overview | +| 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 | ## Eval traceability -| Eval | Primary best-practice rule(s) | -|------|-------------------------------| -| 01 – canvas-fill-background | 1, 5 | -| 02 – shape-primitives | 1 | -| 03 – path-drawing | 6 | -| 04 – paint-stroke-fill | 7 | -| 05 – linear-gradient | 1, 2 | -| 06 – radial-gradient | 1, 2 | -| 07 – image-display | 1 | -| 08 – text-rendering | 11 | -| 09 – blur-filter | 8 | -| 10 – color-matrix-filter | 8 | -| 11 – reanimated-basic-animation | 2 | -| 12 – derived-value-animation | 2 | -| 13 – animated-color-interpolation | 3 | -| 14 – gesture-pan-interaction | 4 | -| 15 – transforms | 2 | -| 16 – clip-rect-and-path | 13 | -| 17 – blend-mode | 12 | -| 18 – svg-path-rendering | 6 | -| 19 – runtime-effect-shader | 9 | -| 20 – canvas-snapshot | 10 | +| Eval | Primary best-practice rule(s) | +| --------------------------------- | ----------------------------- | +| 01 – canvas-fill-background | 1, 5 | +| 02 – shape-primitives | 1 | +| 03 – path-drawing | 6 | +| 04 – paint-stroke-fill | 7 | +| 05 – linear-gradient | 1, 2 | +| 06 – radial-gradient | 1, 2 | +| 07 – image-display | 1 | +| 08 – text-rendering | 11 | +| 09 – blur-filter | 8 | +| 10 – color-matrix-filter | 8 | +| 11 – reanimated-basic-animation | 2 | +| 12 – derived-value-animation | 2 | +| 13 – animated-color-interpolation | 3 | +| 14 – gesture-pan | 4 | +| 15 – transforms | 2 | +| 16 – clip-rect-and-path | 13 | +| 17 – blend-mode | 12 | +| 18 – svg-path-rendering | 6 | +| 19 – runtime-effect-shader | 9 | +| 20 – canvas-snapshot | 10 | ## Common issue clusters -| Tag | Examples | -|-----|---------| -| API misuse | Using `interpolateColor` from Reanimated instead of `interpolateColors` from Skia; using `createAnimatedComponent` unnecessarily | -| Setup | Forgetting `` wrapper; rendering RN `` instead of Skia `` | -| Performance | Per-render path construction instead of memoization; creating `SkPaint` objects inside render | -| Platform parity | `y` origin for `Text` is baseline not top-left (differs from RN Text) | -| Edge case | Not handling `useImage` null state before image is loaded | +| Tag | Examples | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| API misuse | Using `interpolateColor` from Reanimated instead of `interpolateColors` from Skia; using `createAnimatedComponent` unnecessarily | +| Setup | Forgetting `` wrapper; rendering RN `` instead of Skia `` | +| Performance | Per-render path construction instead of memoization; creating `SkPaint` objects inside render | +| Platform parity | `y` origin for `Text` is baseline not top-left (differs from RN Text) | +| Edge case | Not handling `useImage` null state before image is loaded | From c44d24d24ddccf76a7006d69b54d2f5e6c1e2e40 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 19:54:10 +0200 Subject: [PATCH 04/14] feat: add Skia evals starter templates --- .../app/App.tsx | 13 ++++++ .../02-rn-skia-shape-primitives/app/App.tsx | 12 ++++++ .../reference/App.tsx | 25 ++++++++---- .../skia/03-rn-skia-path-drawing/app/App.tsx | 11 +++++ .../03-rn-skia-path-drawing/reference/App.tsx | 10 ++--- .../04-rn-skia-paint-stroke-fill/app/App.tsx | 12 ++++++ .../reference/App.tsx | 27 ++++++++----- .../05-rn-skia-linear-gradient/app/App.tsx | 6 +++ .../06-rn-skia-radial-gradient/app/App.tsx | 11 +++++ .../reference/App.tsx | 14 +++++-- .../skia/07-rn-skia-image-display/app/App.tsx | 14 +++++++ .../08-rn-skia-text-rendering/app/App.tsx | 18 +++++++++ .../reference/App.tsx | 12 ++++-- evals/skia/09-rn-skia-blur-filter/app/App.tsx | 24 +++++++++++ .../09-rn-skia-blur-filter/reference/App.tsx | 10 ++--- .../app/App.tsx | 19 +++++++++ .../reference/App.tsx | 27 ++++++++----- .../app/App.tsx | 12 ++++++ .../reference/App.tsx | 4 +- .../app/App.tsx | 12 ++++++ .../reference/App.tsx | 21 +++++++--- .../app/App.tsx | 5 +++ evals/skia/14-rn-skia-gesture-pan/app/App.tsx | 11 +++++ evals/skia/15-rn-skia-transforms/app/App.tsx | 9 +++++ .../16-rn-skia-clip-rect-and-path/app/App.tsx | 12 ++++++ evals/skia/17-rn-skia-blend-mode/app/App.tsx | 9 +++++ .../18-rn-skia-svg-path-rendering/app/App.tsx | 12 ++++++ .../app/App.tsx | 9 +++++ .../20-rn-skia-canvas-snapshot/app/App.tsx | 40 +++++++++++++++++++ 29 files changed, 371 insertions(+), 50 deletions(-) create mode 100644 evals/skia/01-rn-skia-canvas-fill-background/app/App.tsx create mode 100644 evals/skia/02-rn-skia-shape-primitives/app/App.tsx create mode 100644 evals/skia/03-rn-skia-path-drawing/app/App.tsx create mode 100644 evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx create mode 100644 evals/skia/05-rn-skia-linear-gradient/app/App.tsx create mode 100644 evals/skia/06-rn-skia-radial-gradient/app/App.tsx create mode 100644 evals/skia/07-rn-skia-image-display/app/App.tsx create mode 100644 evals/skia/08-rn-skia-text-rendering/app/App.tsx create mode 100644 evals/skia/09-rn-skia-blur-filter/app/App.tsx create mode 100644 evals/skia/10-rn-skia-color-matrix-filter/app/App.tsx create mode 100644 evals/skia/11-rn-skia-reanimated-basic-animation/app/App.tsx create mode 100644 evals/skia/12-rn-skia-derived-value-animation/app/App.tsx create mode 100644 evals/skia/13-rn-skia-animated-color-interpolation/app/App.tsx create mode 100644 evals/skia/14-rn-skia-gesture-pan/app/App.tsx create mode 100644 evals/skia/15-rn-skia-transforms/app/App.tsx create mode 100644 evals/skia/16-rn-skia-clip-rect-and-path/app/App.tsx create mode 100644 evals/skia/17-rn-skia-blend-mode/app/App.tsx create mode 100644 evals/skia/18-rn-skia-svg-path-rendering/app/App.tsx create mode 100644 evals/skia/19-rn-skia-runtime-effect-shader/app/App.tsx create mode 100644 evals/skia/20-rn-skia-canvas-snapshot/app/App.tsx 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 00000000..b54b54e8 --- /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/02-rn-skia-shape-primitives/app/App.tsx b/evals/skia/02-rn-skia-shape-primitives/app/App.tsx new file mode 100644 index 00000000..47facaba --- /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/reference/App.tsx b/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx index bb780031..a3c28a79 100644 --- a/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx +++ b/evals/skia/02-rn-skia-shape-primitives/reference/App.tsx @@ -1,4 +1,12 @@ -import { Canvas, Circle, Fill, Line, Rect, RoundedRect, vec } from '@shopify/react-native-skia' +import { + Canvas, + Circle, + Fill, + Line, + Rect, + RoundedRect, + vec, +} from '@shopify/react-native-skia' const PADDING = 24 const SHAPE_SIZE = 80 @@ -6,21 +14,21 @@ const SHAPE_SIZE = 80 export default function App() { return ( - + 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 00000000..6988c741 --- /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/reference/App.tsx b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx index 2c737b89..ebaac7fd 100644 --- a/evals/skia/03-rn-skia-path-drawing/reference/App.tsx +++ b/evals/skia/03-rn-skia-path-drawing/reference/App.tsx @@ -13,14 +13,14 @@ const path = (() => { export default function App() { return ( - + ) 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 00000000..28ea988e --- /dev/null +++ b/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx @@ -0,0 +1,12 @@ +import { Platform } from 'react-native' +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/reference/App.tsx b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx index 48fe0ae6..7713295e 100644 --- a/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx +++ b/evals/skia/04-rn-skia-paint-stroke-fill/reference/App.tsx @@ -1,4 +1,11 @@ -import { Canvas, Circle, Fill, Group, Paint, Rect } from '@shopify/react-native-skia' +import { + Canvas, + Circle, + Fill, + Group, + Paint, + Rect, +} from '@shopify/react-native-skia' const SIZE = 300 const CX = SIZE / 2 @@ -8,18 +15,20 @@ const INNER_STROKE = 8 export default function App() { return ( - - + + - - - - + + + + - + - + 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 00000000..5bb05d5f --- /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/06-rn-skia-radial-gradient/app/App.tsx b/evals/skia/06-rn-skia-radial-gradient/app/App.tsx new file mode 100644 index 00000000..5657f428 --- /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/reference/App.tsx b/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx index 970867d7..c9b34a5c 100644 --- a/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx +++ b/evals/skia/06-rn-skia-radial-gradient/reference/App.tsx @@ -1,4 +1,10 @@ -import { Canvas, Circle, Fill, RadialGradient, vec } from '@shopify/react-native-skia' +import { + Canvas, + Circle, + Fill, + RadialGradient, + vec, +} from '@shopify/react-native-skia' const SIZE = 320 const CX = SIZE / 2 @@ -7,8 +13,10 @@ const R = 120 export default function App() { return ( - - + + +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#0f172a', + }, +}) 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 00000000..36a32115 --- /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/reference/App.tsx b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx index 1b25f8f1..1c42e5e6 100644 --- a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx +++ b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx @@ -1,5 +1,11 @@ import { Platform } from 'react-native' -import { Canvas, Fill, Text, matchFont, useCanvasSize } from '@shopify/react-native-skia' +import { + Canvas, + Fill, + Text, + matchFont, + useCanvasSize, +} from '@shopify/react-native-skia' const FONT_SIZE = 32 @@ -19,13 +25,13 @@ function CenteredText() { const x = (width - textWidth) / 2 const y = height / 2 + FONT_SIZE / 2 - return + return } export default function App() { return ( - + ) 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 00000000..bcdd2b45 --- /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/reference/App.tsx b/evals/skia/09-rn-skia-blur-filter/reference/App.tsx index 86cda9c8..936c6ee5 100644 --- a/evals/skia/09-rn-skia-blur-filter/reference/App.tsx +++ b/evals/skia/09-rn-skia-blur-filter/reference/App.tsx @@ -13,19 +13,17 @@ export default function App() { return ( - + - + - + {blur > 0 && } - - Blur: {blur === 0 ? 'off' : blur} - + Blur: {blur === 0 ? 'off' : blur} ) 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 00000000..3087505d --- /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/reference/App.tsx b/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx index 985d09cb..cc4adaa6 100644 --- a/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx +++ b/evals/skia/10-rn-skia-color-matrix-filter/reference/App.tsx @@ -1,12 +1,16 @@ import { useState } from 'react' import { StyleSheet, Text, TouchableOpacity, View } from 'react-native' -import { Canvas, Circle, ColorMatrix, Fill, Group } from '@shopify/react-native-skia' +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, + 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() { @@ -15,17 +19,20 @@ export default function App() { return ( - + {filtered && } - - - + + + - setFiltered((v) => !v)}> + setFiltered((v) => !v)} + > Grayscale: {filtered ? 'ON' : 'OFF'} 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 00000000..f4db03a8 --- /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/reference/App.tsx b/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx index 2df691f8..e31d541b 100644 --- a/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx +++ b/evals/skia/11-rn-skia-reanimated-basic-animation/reference/App.tsx @@ -20,8 +20,8 @@ export default function App() { return ( - - + + ) } 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 00000000..df41e243 --- /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/reference/App.tsx b/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx index dae0f174..9f62ba1c 100644 --- a/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx +++ b/evals/skia/12-rn-skia-derived-value-animation/reference/App.tsx @@ -1,7 +1,12 @@ 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' +import { + useDerivedValue, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated' const MIN_R = 20 const MAX_R = 100 @@ -12,16 +17,22 @@ export default function App() { const progress = useSharedValue(0) useEffect(() => { - progress.value = withRepeat(withTiming(1, { duration: DURATION_MS }), -1, true) + 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) + const cy = useDerivedValue( + () => height / 2 + (MAX_R - MIN_R) * (1 - progress.value) * 0.4 + ) return ( - - + + ) } 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 00000000..af637b90 --- /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/14-rn-skia-gesture-pan/app/App.tsx b/evals/skia/14-rn-skia-gesture-pan/app/App.tsx new file mode 100644 index 00000000..1bd40ba9 --- /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/15-rn-skia-transforms/app/App.tsx b/evals/skia/15-rn-skia-transforms/app/App.tsx new file mode 100644 index 00000000..6be6ffb2 --- /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/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 00000000..5c1b481f --- /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/17-rn-skia-blend-mode/app/App.tsx b/evals/skia/17-rn-skia-blend-mode/app/App.tsx new file mode 100644 index 00000000..189637f9 --- /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/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 00000000..60a72d66 --- /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/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 00000000..630cffc6 --- /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/20-rn-skia-canvas-snapshot/app/App.tsx b/evals/skia/20-rn-skia-canvas-snapshot/app/App.tsx new file mode 100644 index 00000000..e0ca32b5 --- /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', + }, +}) From 300f7f1878405895d33f14e83b64ef68d84aed0a Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 20:17:45 +0200 Subject: [PATCH 05/14] feat: consistently require the canvas to be present --- evals/skia/02-rn-skia-shape-primitives/requirements.yaml | 3 +++ evals/skia/03-rn-skia-path-drawing/requirements.yaml | 3 +++ evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml | 3 +++ evals/skia/05-rn-skia-linear-gradient/requirements.yaml | 3 +++ evals/skia/06-rn-skia-radial-gradient/requirements.yaml | 3 +++ evals/skia/07-rn-skia-image-display/requirements.yaml | 3 +++ evals/skia/08-rn-skia-text-rendering/requirements.yaml | 3 +++ evals/skia/09-rn-skia-blur-filter/requirements.yaml | 3 +++ evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml | 3 +++ .../11-rn-skia-reanimated-basic-animation/requirements.yaml | 3 +++ .../skia/12-rn-skia-derived-value-animation/requirements.yaml | 3 +++ .../13-rn-skia-animated-color-interpolation/requirements.yaml | 3 +++ evals/skia/14-rn-skia-gesture-pan/requirements.yaml | 3 +++ evals/skia/15-rn-skia-transforms/requirements.yaml | 3 +++ evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml | 3 +++ evals/skia/17-rn-skia-blend-mode/requirements.yaml | 3 +++ evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml | 3 +++ evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml | 3 +++ evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml | 3 +++ 19 files changed, 57 insertions(+) diff --git a/evals/skia/02-rn-skia-shape-primitives/requirements.yaml b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml index c9e486a5..e81b3e1d 100644 --- a/evals/skia/02-rn-skia-shape-primitives/requirements.yaml +++ b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/03-rn-skia-path-drawing/requirements.yaml b/evals/skia/03-rn-skia-path-drawing/requirements.yaml index 12f2e51a..0f0182cb 100644 --- a/evals/skia/03-rn-skia-path-drawing/requirements.yaml +++ b/evals/skia/03-rn-skia-path-drawing/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml index 202a2cce..c433c944 100644 --- a/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml +++ b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/05-rn-skia-linear-gradient/requirements.yaml b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml index 577d0200..f0af961c 100644 --- a/evals/skia/05-rn-skia-linear-gradient/requirements.yaml +++ b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/06-rn-skia-radial-gradient/requirements.yaml b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml index 14581a34..9f89357b 100644 --- a/evals/skia/06-rn-skia-radial-gradient/requirements.yaml +++ b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/07-rn-skia-image-display/requirements.yaml b/evals/skia/07-rn-skia-image-display/requirements.yaml index c19ad633..7cd032db 100644 --- a/evals/skia/07-rn-skia-image-display/requirements.yaml +++ b/evals/skia/07-rn-skia-image-display/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/08-rn-skia-text-rendering/requirements.yaml b/evals/skia/08-rn-skia-text-rendering/requirements.yaml index 0c75fe32..8f636e93 100644 --- a/evals/skia/08-rn-skia-text-rendering/requirements.yaml +++ b/evals/skia/08-rn-skia-text-rendering/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/09-rn-skia-blur-filter/requirements.yaml b/evals/skia/09-rn-skia-blur-filter/requirements.yaml index 77b769a6..a6f14e98 100644 --- a/evals/skia/09-rn-skia-blur-filter/requirements.yaml +++ b/evals/skia/09-rn-skia-blur-filter/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml index ed8c2926..c4b08b44 100644 --- a/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml +++ b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml index e781656f..c443490b 100644 --- a/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml +++ b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml index c4ba4216..66f58a3d 100644 --- a/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml +++ b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml index 77aa5a28..e82cbbaf 100644 --- a/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml +++ b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/14-rn-skia-gesture-pan/requirements.yaml b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml index a3243791..3e266a2a 100644 --- a/evals/skia/14-rn-skia-gesture-pan/requirements.yaml +++ b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/15-rn-skia-transforms/requirements.yaml b/evals/skia/15-rn-skia-transforms/requirements.yaml index 77177589..0db561ab 100644 --- a/evals/skia/15-rn-skia-transforms/requirements.yaml +++ b/evals/skia/15-rn-skia-transforms/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 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 22406b72..b6089733 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 @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/17-rn-skia-blend-mode/requirements.yaml b/evals/skia/17-rn-skia-blend-mode/requirements.yaml index 23728f0e..e2422dd8 100644 --- a/evals/skia/17-rn-skia-blend-mode/requirements.yaml +++ b/evals/skia/17-rn-skia-blend-mode/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml index 4c3de651..a8b6bfd9 100644 --- a/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml +++ b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml index 90487526..d1a344cd 100644 --- a/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml +++ b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 diff --git a/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml index b9497b4b..d5021a02 100644 --- a/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml +++ b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml @@ -1,5 +1,8 @@ version: 1 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 From 89e0ca43dec7eb638461f505a90ee7490d126ae0 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 20:19:19 +0200 Subject: [PATCH 06/14] chore: add missing inputs key --- evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml | 3 +++ evals/skia/02-rn-skia-shape-primitives/requirements.yaml | 3 +++ evals/skia/03-rn-skia-path-drawing/requirements.yaml | 3 +++ evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml | 3 +++ evals/skia/05-rn-skia-linear-gradient/requirements.yaml | 3 +++ evals/skia/06-rn-skia-radial-gradient/requirements.yaml | 3 +++ evals/skia/07-rn-skia-image-display/requirements.yaml | 3 +++ evals/skia/08-rn-skia-text-rendering/requirements.yaml | 3 +++ evals/skia/09-rn-skia-blur-filter/requirements.yaml | 3 +++ evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml | 3 +++ .../11-rn-skia-reanimated-basic-animation/requirements.yaml | 3 +++ .../skia/12-rn-skia-derived-value-animation/requirements.yaml | 3 +++ .../13-rn-skia-animated-color-interpolation/requirements.yaml | 3 +++ evals/skia/14-rn-skia-gesture-pan/requirements.yaml | 3 +++ evals/skia/15-rn-skia-transforms/requirements.yaml | 3 +++ evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml | 3 +++ evals/skia/17-rn-skia-blend-mode/requirements.yaml | 3 +++ evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml | 3 +++ evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml | 3 +++ evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml | 3 +++ 20 files changed, 60 insertions(+) diff --git a/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml b/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml index bc71193a..39e352ef 100644 --- a/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml +++ b/evals/skia/01-rn-skia-canvas-fill-background/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/02-rn-skia-shape-primitives/requirements.yaml b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml index e81b3e1d..a611161a 100644 --- a/evals/skia/02-rn-skia-shape-primitives/requirements.yaml +++ b/evals/skia/02-rn-skia-shape-primitives/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/03-rn-skia-path-drawing/requirements.yaml b/evals/skia/03-rn-skia-path-drawing/requirements.yaml index 0f0182cb..69489251 100644 --- a/evals/skia/03-rn-skia-path-drawing/requirements.yaml +++ b/evals/skia/03-rn-skia-path-drawing/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml index c433c944..8df059fd 100644 --- a/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml +++ b/evals/skia/04-rn-skia-paint-stroke-fill/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/05-rn-skia-linear-gradient/requirements.yaml b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml index f0af961c..d89553b5 100644 --- a/evals/skia/05-rn-skia-linear-gradient/requirements.yaml +++ b/evals/skia/05-rn-skia-linear-gradient/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/06-rn-skia-radial-gradient/requirements.yaml b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml index 9f89357b..c2a9059b 100644 --- a/evals/skia/06-rn-skia-radial-gradient/requirements.yaml +++ b/evals/skia/06-rn-skia-radial-gradient/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/07-rn-skia-image-display/requirements.yaml b/evals/skia/07-rn-skia-image-display/requirements.yaml index 7cd032db..3017e33c 100644 --- a/evals/skia/07-rn-skia-image-display/requirements.yaml +++ b/evals/skia/07-rn-skia-image-display/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/08-rn-skia-text-rendering/requirements.yaml b/evals/skia/08-rn-skia-text-rendering/requirements.yaml index 8f636e93..fb6b7e4d 100644 --- a/evals/skia/08-rn-skia-text-rendering/requirements.yaml +++ b/evals/skia/08-rn-skia-text-rendering/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/09-rn-skia-blur-filter/requirements.yaml b/evals/skia/09-rn-skia-blur-filter/requirements.yaml index a6f14e98..7a0bb69b 100644 --- a/evals/skia/09-rn-skia-blur-filter/requirements.yaml +++ b/evals/skia/09-rn-skia-blur-filter/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml index c4b08b44..0ed8f29d 100644 --- a/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml +++ b/evals/skia/10-rn-skia-color-matrix-filter/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml index c443490b..40cad57e 100644 --- a/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml +++ b/evals/skia/11-rn-skia-reanimated-basic-animation/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml index 66f58a3d..8aed999c 100644 --- a/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml +++ b/evals/skia/12-rn-skia-derived-value-animation/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml index e82cbbaf..51336e4f 100644 --- a/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml +++ b/evals/skia/13-rn-skia-animated-color-interpolation/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/14-rn-skia-gesture-pan/requirements.yaml b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml index 3e266a2a..cda86e24 100644 --- a/evals/skia/14-rn-skia-gesture-pan/requirements.yaml +++ b/evals/skia/14-rn-skia-gesture-pan/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/15-rn-skia-transforms/requirements.yaml b/evals/skia/15-rn-skia-transforms/requirements.yaml index 0db561ab..de5fe9c6 100644 --- a/evals/skia/15-rn-skia-transforms/requirements.yaml +++ b/evals/skia/15-rn-skia-transforms/requirements.yaml @@ -1,4 +1,7 @@ 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. 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 b6089733..5e437f64 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 @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/17-rn-skia-blend-mode/requirements.yaml b/evals/skia/17-rn-skia-blend-mode/requirements.yaml index e2422dd8..4f99560b 100644 --- a/evals/skia/17-rn-skia-blend-mode/requirements.yaml +++ b/evals/skia/17-rn-skia-blend-mode/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml index a8b6bfd9..9ff017b6 100644 --- a/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml +++ b/evals/skia/18-rn-skia-svg-path-rendering/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml index d1a344cd..14d8f4c2 100644 --- a/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml +++ b/evals/skia/19-rn-skia-runtime-effect-shader/requirements.yaml @@ -1,4 +1,7 @@ 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. diff --git a/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml index d5021a02..65bddff7 100644 --- a/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml +++ b/evals/skia/20-rn-skia-canvas-snapshot/requirements.yaml @@ -1,4 +1,7 @@ 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. From 27b50a5cb15eb111061bc10c3aacedc50f9eb3ea Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 20:21:23 +0200 Subject: [PATCH 07/14] fix: remove obsolete & inconsistent pieces --- evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx | 1 - evals/skia/README.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 index 28ea988e..09a8ea01 100644 --- a/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx +++ b/evals/skia/04-rn-skia-paint-stroke-fill/app/App.tsx @@ -1,4 +1,3 @@ -import { Platform } from 'react-native' import { Canvas, Fill } from '@shopify/react-native-skia' const SIZE = 300 diff --git a/evals/skia/README.md b/evals/skia/README.md index c5ffb30b..023f9bcb 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -42,7 +42,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 03 – path-drawing | 6 | | 04 – paint-stroke-fill | 7 | | 05 – linear-gradient | 1, 2 | -| 06 – radial-gradient | 1, 2 | +| 06 – radial-gradient | 1 | | 07 – image-display | 1 | | 08 – text-rendering | 11 | | 09 – blur-filter | 8 | From a34b0b1e6887fe7c95e204b37679ed0331a30a87 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 20:30:29 +0200 Subject: [PATCH 08/14] feat: add evals for: Group layer + Paint + Blur, SweepGradient, ParagraphBuilder --- .../21-rn-skia-sweep-gradient/app/App.tsx | 14 ++++++ .../skia/21-rn-skia-sweep-gradient/prompt.md | 1 + .../reference/App.tsx | 25 ++++++++++ .../requirements.yaml | 14 ++++++ .../22-rn-skia-group-layer-effect/app/App.tsx | 12 +++++ .../22-rn-skia-group-layer-effect/prompt.md | 1 + .../reference/App.tsx | 31 ++++++++++++ .../requirements.yaml | 14 ++++++ .../app/App.tsx | 11 +++++ .../prompt.md | 1 + .../reference/App.tsx | 47 +++++++++++++++++++ .../requirements.yaml | 16 +++++++ evals/skia/README.md | 29 +++++++++++- 13 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 evals/skia/21-rn-skia-sweep-gradient/app/App.tsx create mode 100644 evals/skia/21-rn-skia-sweep-gradient/prompt.md create mode 100644 evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx create mode 100644 evals/skia/21-rn-skia-sweep-gradient/requirements.yaml create mode 100644 evals/skia/22-rn-skia-group-layer-effect/app/App.tsx create mode 100644 evals/skia/22-rn-skia-group-layer-effect/prompt.md create mode 100644 evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx create mode 100644 evals/skia/22-rn-skia-group-layer-effect/requirements.yaml create mode 100644 evals/skia/23-rn-skia-paragraph-styled-text/app/App.tsx create mode 100644 evals/skia/23-rn-skia-paragraph-styled-text/prompt.md create mode 100644 evals/skia/23-rn-skia-paragraph-styled-text/reference/App.tsx create mode 100644 evals/skia/23-rn-skia-paragraph-styled-text/requirements.yaml 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 00000000..b5c6d0f9 --- /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 00000000..bb309967 --- /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 00000000..2e21308c --- /dev/null +++ b/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx @@ -0,0 +1,25 @@ +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 + +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 00000000..c1b655ad --- /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 00000000..b1715bde --- /dev/null +++ b/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx @@ -0,0 +1,12 @@ +import { Canvas, Fill } from '@shopify/react-native-skia' + +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 00000000..377b81c2 --- /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 00000000..37e50ff9 --- /dev/null +++ b/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx @@ -0,0 +1,31 @@ +import { + Blur, + Canvas, + Circle, + Fill, + Group, + Paint, +} from '@shopify/react-native-skia' + +const SIZE = 320 +const CY = 160 + +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 00000000..6cd07e07 --- /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 00000000..c41881ca --- /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 00000000..635a1880 --- /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 00000000..c1f1a5f7 --- /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 00000000..cfaaa314 --- /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/README.md b/evals/skia/README.md index 023f9bcb..6882cacd 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -9,9 +9,13 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D - 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 +- 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 @@ -32,6 +36,9 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 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 | +| 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 | ## Eval traceability @@ -41,7 +48,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 02 – shape-primitives | 1 | | 03 – path-drawing | 6 | | 04 – paint-stroke-fill | 7 | -| 05 – linear-gradient | 1, 2 | +| 05 – linear-gradient | 1, 2, 3 | | 06 – radial-gradient | 1 | | 07 – image-display | 1 | | 08 – text-rendering | 11 | @@ -57,6 +64,26 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 18 – svg-path-rendering | 6 | | 19 – runtime-effect-shader | 9 | | 20 – canvas-snapshot | 10 | +| 21 – sweep-gradient | 14 | +| 22 – group-layer-effect | 15 | +| 23 – paragraph-styled-text | 16 | + +## API coverage notes + +The pack now covers all four built-in gradient shaders (linear, radial, sweep; conical remains untested). Major APIs still intentionally omitted — either asset-heavy, niche, or overlapping existing evals: + +| API area | Examples | Why omitted | +| ------------------------- | ------------------------------------------ | -------------------------------------------------------------------------- | +| Conical gradient | `TwoPointConicalGradient` | Overlaps sweep/radial family; add only if conical spotlight effects matter | +| Mask compositing | `` with `alpha` or `luminance` mode | Distinct from clip; good candidate if compositing pack expands | +| Neumorphism shadows | `Box` + `BoxShadow`, `Shadow`/`DropShadow` | UI-specific; overlaps blur/filter evals conceptually | +| 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 | +| Lottie playback | `Skottie`, `Skia.Skottie.Make` | Requires Lottie JSON + optional asset slots | +| Recorded drawing | `Picture`, `createPicture` | Immediate-mode performance API; low LLM failure signal | +| Fitbox / Group clip props | `FitBox`, `Group clip` / `invertClip` | Overlap path/SVG evals; `FitBox` is a strong future add | +| Path / mask filters | `DashPathEffect`, `BlurMask`, morphology | Lower-level paint modifiers; narrow use cases | ## Common issue clusters From 509eb3bcb34b7ef462ca94b028fce52b8bcd2066 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 20 May 2026 20:47:26 +0200 Subject: [PATCH 09/14] feat: eval for imperative canvas APIs --- .../app/App.tsx | 19 +++++++ .../24-rn-skia-picture-save-restore/prompt.md | 1 + .../reference/App.tsx | 57 +++++++++++++++++++ .../requirements.yaml | 20 +++++++ evals/skia/README.md | 4 +- 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 evals/skia/24-rn-skia-picture-save-restore/app/App.tsx create mode 100644 evals/skia/24-rn-skia-picture-save-restore/prompt.md create mode 100644 evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx create mode 100644 evals/skia/24-rn-skia-picture-save-restore/requirements.yaml 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 00000000..0ce40745 --- /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 00000000..455e49e4 --- /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 00000000..a856677c --- /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 00000000..302e3484 --- /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/README.md b/evals/skia/README.md index 6882cacd..57a1a20d 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -15,6 +15,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D - 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 @@ -39,6 +40,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 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 | +| 17 | Isolate imperative canvas transforms with `canvas.save()` / `canvas.restore()` inside a recorded `Picture` | shapes/pictures | ## Eval traceability @@ -67,6 +69,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 21 – sweep-gradient | 14 | | 22 – group-layer-effect | 15 | | 23 – paragraph-styled-text | 16 | +| 24 – picture-save-restore | 17 | ## API coverage notes @@ -81,7 +84,6 @@ The pack now covers all four built-in gradient shaders (linear, radial, sweep; c | 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 | | Lottie playback | `Skottie`, `Skia.Skottie.Make` | Requires Lottie JSON + optional asset slots | -| Recorded drawing | `Picture`, `createPicture` | Immediate-mode performance API; low LLM failure signal | | Fitbox / Group clip props | `FitBox`, `Group clip` / `invertClip` | Overlap path/SVG evals; `FitBox` is a strong future add | | Path / mask filters | `DashPathEffect`, `BlurMask`, morphology | Lower-level paint modifiers; narrow use cases | From aadaceedea13e76cfc3d8c8d26f38e6b73202563 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 21 May 2026 15:33:23 +0200 Subject: [PATCH 10/14] refactor: consistently type port as non-nullable --- runner/evaluators/llm/run.ts | 2 +- runner/solver/pipeline.ts | 7 ++-- runner/utils/opencode-session.ts | 55 +++++++++++++++++--------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/runner/evaluators/llm/run.ts b/runner/evaluators/llm/run.ts index 4e105a06..9ee6b8ae 100644 --- a/runner/evaluators/llm/run.ts +++ b/runner/evaluators/llm/run.ts @@ -7,7 +7,7 @@ import type { LoadedFile } from 'runner/utils/fs' type LlmJudgeStageOptions = { model: string timeout: number - port?: number + port: number directory?: string requirementIds?: string[] } diff --git a/runner/solver/pipeline.ts b/runner/solver/pipeline.ts index 6f64b6d2..898a142d 100644 --- a/runner/solver/pipeline.ts +++ b/runner/solver/pipeline.ts @@ -4,7 +4,7 @@ import { type LoadedFile } from 'runner/utils/fs' type SolverStageOptions = { solverModel: string timeout: number - port?: number + port: number } /* @@ -28,9 +28,6 @@ export async function runSolverStage( return { summary: result.summary, opencodeSession: result.opencodeSession, - files: await materializeFiles( - workingDir, - result.files - ), + files: await materializeFiles(workingDir, result.files), } } diff --git a/runner/utils/opencode-session.ts b/runner/utils/opencode-session.ts index 4872c179..90079543 100644 --- a/runner/utils/opencode-session.ts +++ b/runner/utils/opencode-session.ts @@ -83,7 +83,10 @@ function extractMessageContent(parts: Part[]) { } if (part.type === 'tool') { - if (part.state.status === 'completed' && part.state.output.trim().length > 0) { + if ( + part.state.status === 'completed' && + part.state.output.trim().length > 0 + ) { segments.push(part.state.output) } continue @@ -181,12 +184,14 @@ function uniqueTouchedFiles( } function summarizeChanges( - sessionSummary: { - additions: number - deletions: number - files: number - diffs?: FileDiff[] - } | undefined, + sessionSummary: + | { + additions: number + deletions: number + files: number + diffs?: FileDiff[] + } + | undefined, sessionDiffs: FileDiff[] ) { const sessionDiffAdditions = sessionDiffs.reduce( @@ -210,7 +215,7 @@ function summarizeChanges( export async function collectOpencodeSessionSnapshot(params: { sessionId?: string - port?: number + port: number directory?: string }): Promise { if (!params.sessionId) { @@ -228,21 +233,21 @@ export async function collectOpencodeSessionSnapshot(params: { try { const [sessionResponse, messagesResponse, sessionDiffsResponse] = await Promise.all([ - client.session.get({ - path: { id: params.sessionId }, - query: { directory: params.directory }, - throwOnError: true, - }), - client.session.messages({ - path: { id: params.sessionId }, - query: { directory: params.directory, limit: 500 }, - throwOnError: true, - }), - client.session.diff({ - path: { id: params.sessionId }, - query: { directory: params.directory }, - throwOnError: true, - }), + client.session.get({ + path: { id: params.sessionId }, + query: { directory: params.directory }, + throwOnError: true, + }), + client.session.messages({ + path: { id: params.sessionId }, + query: { directory: params.directory, limit: 500 }, + throwOnError: true, + }), + client.session.diff({ + path: { id: params.sessionId }, + query: { directory: params.directory }, + throwOnError: true, + }), ]) session = sessionResponse.data @@ -286,9 +291,7 @@ export async function collectOpencodeSessionSnapshot(params: { const messageSnapshots = typedMessages .map((message) => summarizeMessage(message)) - .sort((first, second) => - first.createdAt.localeCompare(second.createdAt) - ) + .sort((first, second) => first.createdAt.localeCompare(second.createdAt)) const userTurns = messageSnapshots.filter( (message) => message.role === 'user' From ec3c856e043ea1df2aea819e52cde0c7e9819066 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 21 May 2026 15:34:41 +0200 Subject: [PATCH 11/14] chore: update Skia README --- evals/skia/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evals/skia/README.md b/evals/skia/README.md index 57a1a20d..76d6975a 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -73,7 +73,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D ## API coverage notes -The pack now covers all four built-in gradient shaders (linear, radial, sweep; conical remains untested). Major APIs still intentionally omitted — either asset-heavy, niche, or overlapping existing evals: +The pack now covers three built-in gradient shaders (linear, radial, sweep). Major APIs still intentionally omitted — either asset-heavy, niche, or overlapping existing evals: | API area | Examples | Why omitted | | ------------------------- | ------------------------------------------ | -------------------------------------------------------------------------- | @@ -83,7 +83,6 @@ The pack now covers all four built-in gradient shaders (linear, radial, sweep; c | 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 | -| Lottie playback | `Skottie`, `Skia.Skottie.Make` | Requires Lottie JSON + optional asset slots | | Fitbox / Group clip props | `FitBox`, `Group clip` / `invertClip` | Overlap path/SVG evals; `FitBox` is a strong future add | | Path / mask filters | `DashPathEffect`, `BlurMask`, morphology | Lower-level paint modifiers; narrow use cases | From 4e7ebb5a9e2d17d8215375aa8e08cb1174453906 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 21 May 2026 15:35:46 +0200 Subject: [PATCH 12/14] chore: add missing COLORS to Skia eval 21 --- evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx b/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx index 2e21308c..9d07e022 100644 --- a/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx +++ b/evals/skia/21-rn-skia-sweep-gradient/reference/App.tsx @@ -10,6 +10,7 @@ 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 ( From e052b5a64f3a78c9ae491194535c1a0d0842ef4c Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 21 May 2026 15:36:27 +0200 Subject: [PATCH 13/14] chore: unify declarations in Skia eval 22 --- evals/skia/22-rn-skia-group-layer-effect/app/App.tsx | 3 +++ evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx | 3 +++ 2 files changed, 6 insertions(+) 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 index b1715bde..315b62a4 100644 --- a/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx +++ b/evals/skia/22-rn-skia-group-layer-effect/app/App.tsx @@ -1,5 +1,8 @@ import { Canvas, Fill } from '@shopify/react-native-skia' +const SIZE = 320 +const CY = 160 + const COLOR1 = '#6366f1' const COLOR2 = '#ec4899' 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 index 37e50ff9..f61c8aa6 100644 --- a/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx +++ b/evals/skia/22-rn-skia-group-layer-effect/reference/App.tsx @@ -10,6 +10,9 @@ import { const SIZE = 320 const CY = 160 +const COLOR1 = '#6366f1' +const COLOR2 = '#ec4899' + export default function App() { return ( Date: Thu, 21 May 2026 15:38:38 +0200 Subject: [PATCH 14/14] feat: add Skottie eval --- .../25-rn-skia-lottie-playback/app/App.tsx | 33 +++++++++++ .../skia/25-rn-skia-lottie-playback/prompt.md | 1 + .../reference/App.tsx | 58 +++++++++++++++++++ .../requirements.yaml | 18 ++++++ evals/skia/README.md | 3 + 5 files changed, 113 insertions(+) create mode 100644 evals/skia/25-rn-skia-lottie-playback/app/App.tsx create mode 100644 evals/skia/25-rn-skia-lottie-playback/prompt.md create mode 100644 evals/skia/25-rn-skia-lottie-playback/reference/App.tsx create mode 100644 evals/skia/25-rn-skia-lottie-playback/requirements.yaml 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 00000000..c1c51023 --- /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 00000000..5e137385 --- /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 00000000..3db46a68 --- /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 ( + + + {animation ? : null} + + ) +} 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 00000000..99b26fff --- /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 index 76d6975a..3b18ea2d 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -19,6 +19,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D - 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 @@ -41,6 +42,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 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 | | 17 | Isolate imperative canvas transforms with `canvas.save()` / `canvas.restore()` inside a recorded `Picture` | shapes/pictures | +| 18 | Load Lottie JSON with `Skia.Skottie.Make(JSON.stringify(...))`; drive `` playback with `useClock` and a Reanimated derived frame | skottie | ## Eval traceability @@ -70,6 +72,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 22 – group-layer-effect | 15 | | 23 – paragraph-styled-text | 16 | | 24 – picture-save-restore | 17 | +| 25 – lottie-playback | 1, 2, 18 | ## API coverage notes