{"record":{"id":"c885f4536edaeefe","repo":"remotion-dev/remotion","slug":"name-must-be-a-number-number-tuple","errorCode":null,"errorMessage":"\"${name}\" must be a [number, number] tuple","messagePattern":"\"(.+?)\" must be a \\[number, number\\] tuple","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/effects/src/corner-pin/index.ts","lineNumber":88,"sourceCode":"\tbottomRight: [\n\t\t...(p.bottomRight ?? DEFAULT_BOTTOM_RIGHT),\n\t] as CornerPinUvCoordinate,\n\tbottomLeft: [\n\t\t...(p.bottomLeft ?? DEFAULT_BOTTOM_LEFT),\n\t] as CornerPinUvCoordinate,\n});\n\nconst assertOptionalUvCoordinate = (value: unknown, name: string): void => {\n\tif (value === undefined) {\n\t\treturn;\n\t}\n\n\tif (\n\t\t!Array.isArray(value) ||\n\t\tvalue.length !== 2 ||\n\t\tvalue.some((item) => typeof item !== 'number' || !Number.isFinite(item))\n\t) {\n\t\tthrow new TypeError(`\"${name}\" must be a [number, number] tuple`);\n\t}\n};\n\nconst validateCornerPinParams = (params: CornerPinParams): void => {\n\tassertEffectParamsObject(params, 'Corner pin');\n\tassertOptionalUvCoordinate(params.topLeft, 'topLeft');\n\tassertOptionalUvCoordinate(params.topRight, 'topRight');\n\tassertOptionalUvCoordinate(params.bottomRight, 'bottomRight');\n\tassertOptionalUvCoordinate(params.bottomLeft, 'bottomLeft');\n};\n\nexport const cornerPin = createEffect<CornerPinParams, CornerPinState>({\n\ttype: 'dev.remotion.effects.cornerPin',\n\tlabel: 'cornerPin()',\n\tdocumentationLink: 'https://www.remotion.dev/docs/effects/corner-pin',\n\tbackend: 'webgl2',\n\tcalculateKey: (params) => {\n\t\tconst r = resolve(params);","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/effects/src/corner-pin/index.ts#L70-L106","documentation":"Thrown by the corner-pin effect's assertOptionalUvCoordinate guard when one of the corner props (topLeft, topRight, bottomRight, bottomLeft) is supplied but is not a two-element array of finite numbers. The corner-pin effect maps the four output corners via UV coordinates, so each corner must be a strict [number, number] tuple. Passing objects, shorter/longer arrays, NaN/Infinity, or non-number elements trips this guard before any WebGL work happens.","triggerScenarios":"Calling cornerPin() with topLeft: {x: 0, y: 0} (object instead of array), topLeft: [0] or topLeft: [0, 0, 0] (wrong arity), topLeft: [0, '1'] (string element), topLeft: [NaN, 0] or topLeft: [Infinity, 0] (non-finite), or topLeft: null (null is not undefined and fails Array.isArray). The check runs inside validateCornerPinParams, which the effect invokes before setup.","commonSituations":"Authoring a corner-pin animation from an external data source whose coordinates come back as objects; serializing tuples through JSON that mutated into objects; passing the output of a math library that returns BigNumbers or strings; copy-pasting a 3-tuple from a 3D tool; animating values through a Spring whose interpolation momentarily yields NaN.","solutions":["Pass each corner as a literal two-element numeric array, e.g. topLeft: [0.25, 0.1].","If coords arrive as objects, map them: topLeft: [pt.x, pt.y] before passing to cornerPin().","Guard against NaN/Infinity upstream: filter coordinates through Number.isFinite before constructing the params object.","Omit the corner entirely to accept the documented default rather than passing null or undefined-like sentinels."],"exampleFix":"// before\ncornerPin({topLeft: {x: 0, y: 0}, bottomRight: [1, 1]});\n\n// after\nconst toUv = (p) => [p.x, p.y];\ncornerPin({topLeft: toUv({x: 0, y: 0}), bottomRight: [1, 1]});","handlingStrategy":"type-guard","validationCode":"const isUv = (v) =>\n  Array.isArray(v) &&\n  v.length === 2 &&\n  v.every((n) => typeof n === 'number' && Number.isFinite(n));\n\nconst safeParams = (p) => {\n  for (const k of ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']) {\n    if (p[k] !== undefined && !isUv(p[k])) {\n      throw new Error(`${k} must be [number, number]`);\n    }\n  }\n  return p;\n};","typeGuard":"import type {CornerPinParams, CornerPinUvCoordinate} from '@remotion/effects';\n\nconst isUvCoordinate = (v: unknown): v is CornerPinUvCoordinate =>\n  Array.isArray(v) &&\n  v.length === 2 &&\n  v.every((n) => typeof n === 'number' && Number.isFinite(n));\n\nconst isCornerPinParams = (p: unknown): p is CornerPinParams => {\n  if (typeof p !== 'object' || p === null) return false;\n  const rec = p as Record<string, unknown>;\n  for (const k of ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']) {\n    if (rec[k] !== undefined && !isUvCoordinate(rec[k])) return false;\n  }\n  return true;\n};","tryCatchPattern":null,"preventionTips":["Annotate corner params as readonly [number, number] at every data boundary so the compiler rejects objects and wrong-arity arrays.","When importing coordinates from external sources, run them through an isUvCoordinate guard before constructing the params object.","Never animate corner values with a Spring/interpolate that can yield NaN; clamp with Number.isFinite."],"tags":["validation","corner-pin","tuple","uv-coordinate","input-validation"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}