makeplane/plane · error · Error

Invalid valueStop: ${anchorStop}. Must be one of ${SHADE_STO

Error message

Invalid valueStop: ${anchorStop}. Must be one of ${SHADE_STOPS.join(", ")}

What it means

When options.valueStop is provided as a number (not 'auto'), generateColorPalette checks it against SHADE_STOPS via isValidShadeStop. The valueStop is the shade anchor (the stop whose lightness equals the input color); it must be one of the supported stops. 'auto' skips this check and computes a dynamic stop; an out-of-range number is rejected.

Source

Thrown at packages/utils/src/theme/palette-generator.ts:142

  const inputOKLCH = hexToOKLCH(normalizedHex);
  const { l: inputL, c: inputC, h: inputH } = inputOKLCH;

  const DEFAULT_LIGHTNESS_MIN = mode === "light" ? DEFAULT_LIGHT_MODE_LIGHTNESS_MIN : DEFAULT_DARK_MODE_LIGHTNESS_MIN;
  const DEFAULT_LIGHTNESS_MAX = mode === "light" ? DEFAULT_LIGHT_MODE_LIGHTNESS_MAX : DEFAULT_DARK_MODE_LIGHTNESS_MAX;

  // Extract options with defaults
  const {
    lightnessMin = DEFAULT_LIGHTNESS_MIN / 100, // Convert to 0-1 scale
    lightnessMax = DEFAULT_LIGHTNESS_MAX / 100, // Convert to 0-1 scale
    valueStop = options.valueStop ?? DEFAULT_VALUE_STOP,
  } = options;

  // Calculate or use provided valueStop
  const anchorStop = valueStop === "auto" ? calculateDynamicValueStop(inputOKLCH) : valueStop;

  // Validate valueStop if provided manually
  if (typeof anchorStop === "number" && !isValidShadeStop(anchorStop)) {
    throw new Error(`Invalid valueStop: ${anchorStop}. Must be one of ${SHADE_STOPS.join(", ")}`);
  }

  // Create lightness distribution with three anchor points
  const distributionAnchors = [
    { stop: SHADE_STOPS[0], lightness: lightnessMax }, // Lightest
    { stop: anchorStop, lightness: inputL }, // Input color
    { stop: SHADE_STOPS[SHADE_STOPS.length - 1], lightness: lightnessMin }, // Darkest
  ];

  // Generate palette by interpolating lightness for each stop
  const palette: Partial<ColorPalette> = {};

  SHADE_STOPS.forEach((stop) => {
    let targetLightness: number;

    // Check if this is an anchor point
    const anchor = distributionAnchors.find((a) => a.stop === stop);
    if (anchor) {

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Use { valueStop: 'auto' } unless you have a specific reason to pin.
  2. If pinning, pick from SHADE_STOPS (import and reference the array rather than hard-coding).
  3. Validate the configured valueStop on app boot and reset to a valid default.

Example fix

// before
generateColorPalette(color, 'light', { valueStop: 99 });

// after
import { SHADE_STOPS } from './theme/palette-generator';
const stop = SHADE_STOPS.includes(config.valueStop) ? config.valueStop : 'auto';
generateColorPalette(color, 'light', { valueStop: stop });
Defensive patterns

Strategy: validation

Validate before calling

import { SHADE_STOPS } from './theme/palette-generator';
const stop = options.valueStop === 'auto' || (typeof options.valueStop === 'number' && SHADE_STOPS.includes(options.valueStop)) ? options.valueStop : 'auto';

Type guard

function isValidStop(s: unknown): boolean { return s === 'auto' || (typeof s === 'number' && SHADE_STOPS.includes(s as number)); }

Try / catch

try { generateColorPalette(c, 'light', { valueStop }); } catch (e) { if (/Invalid valueStop/.test((e as Error).message)) { generateColorPalette(c, 'light', { valueStop: 'auto' }); } else throw e; }

Prevention

When it happens

Trigger: generateColorPalette(color, 'light', { valueStop: 99 }); generateColorPalette(color, 'light', { valueStop: 0 } if 0 is not in SHADE_STOPS); any numeric valueStop outside SHADE_STOPS.

Common situations: Hard-coding a valueStop from an older version when SHADE_STOPS changed; config file with a typo; copy-pasted options from another theme tool with a different scale.

Related errors


AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12). Data as JSON: /api/errors/6a946bc2ed574eca. Report an issue: GitHub.