{"record":{"id":"4ca01b24482c0d67","repo":"remotion-dev/remotion","slug":"stops-must-be-max-stops-but-got-json-st","errorCode":null,"errorMessage":"\"stops\" must be <= ${MAX_STOPS}, but got ${JSON.stringify(stops)}","messagePattern":"\"stops\" must be <= (.+?), but got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/effects/src/exposure.ts","lineNumber":62,"sourceCode":"};\n\nconst resolve = (params: ExposureParams): ExposureResolved => ({\n\tstops: params.stops ?? DEFAULT_STOPS,\n});\n\nconst validateExposureParams = (params: ExposureParams): void => {\n\tassertEffectParamsObject(params, 'Exposure');\n\tassertOptionalFiniteNumber(params.stops, 'stops');\n\n\tconst {stops} = resolve(params);\n\tif (stops < MIN_STOPS) {\n\t\tthrow new TypeError(\n\t\t\t`\"stops\" must be >= ${MIN_STOPS}, but got ${JSON.stringify(stops)}`,\n\t\t);\n\t}\n\n\tif (stops > MAX_STOPS) {\n\t\tthrow new TypeError(\n\t\t\t`\"stops\" must be <= ${MAX_STOPS}, but got ${JSON.stringify(stops)}`,\n\t\t);\n\t}\n};\n\nconst VERTEX_SHADER = /* glsl */ `#version 300 es\nin vec2 aPos;\nin vec2 aUv;\nout vec2 vUv;\n\nvoid main() {\n\tvUv = aUv;\n\tgl_Position = vec4(aPos, 0.0, 1.0);\n}\n`;\n\nconst FRAGMENT_SHADER = /* glsl */ `#version 300 es\nprecision highp float;","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/effects/src/exposure.ts#L44-L80","documentation":"Thrown by validateExposureParams() in exposure.ts when the resolved `stops` value exceeds MAX_STOPS (5). It is a TypeError raised during validateParams, before WebGL setup, giving deterministic feedback. The value is JSON-stringified into the message.","triggerScenarios":"Calling exposure({ stops: x }) with x > 5 (e.g. exposure({ stops: 8 })). Because resolve() only defaults undefined to 0, any explicitly provided number above 5 reaches this branch and throws.","commonSituations":"Easing curves that overshoot past +5 (springs are a common culprit); data-driven stops from a config with no upper bound; composing exposures additively and assuming the effect clamps; UI controls allowing values beyond the documented ±5.","solutions":["Clamp to the documented range before calling: exposure({ stops: Math.max(-5, Math.min(5, value)) }).","Replace overshooting interpolation (spring/bounce) with an easing that respects the [−5, 5] bounds.","Constrain upstream input (data file, schema, slider) so stops can never exceed 5.","Stack multiple exposure() effects if a larger combined shift is required."],"exampleFix":"// before\nexposure({ stops: animatedValue }); // animatedValue peaks at 6.2 -> throws\n\n// after\nexposure({ stops: Math.min(5, Math.max(-5, animatedValue)) });","handlingStrategy":"validation","validationCode":"const EXPOSURE_STOPS_MIN = -5;\nconst EXPOSURE_STOPS_MAX = 5;\n\nconst clampStops = (stops: number): number =>\n  Math.max(EXPOSURE_STOPS_MIN, Math.min(EXPOSURE_STOPS_MAX, stops));\n\n// usage: exposure({ stops: clampStops(animatedValue) })","typeGuard":"const isExposureStops = (v: unknown): v is number =>\n  typeof v === 'number' &&\n  Number.isFinite(v) &&\n  v >= -5 &&\n  v <= 5;\n\n// guard: if (isExposureStops(value)) exposure({ stops: value }); else /* clamp or skip */","tryCatchPattern":null,"preventionTips":["Clamp animated stops to [-5, 5] at the source (interpolate with extrapolateLeft/Right: 'clamp').","Avoid overshooting easings (spring/bounce) on stops.","Constrain upstream data and UI so stops can never exceed 5.","Unit-test the stops stream against the [-5, 5] bounds."],"tags":["validation","exposure","params","stops","range","typescript"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}