d2lang/d2 · error

invalid luminance category

Error message

invalid luminance category

What it means

blendMode maps a luminance category string ("bright", "normal", "dark", "darker") to an SVG mix-blend-mode used by sketch-style overlay themes. The categories come from theme color fields; if a luminance value outside the four known categories reaches this function, it panics. It is an internal invariant protecting against malformed themes.

Source

Thrown at d2renderers/d2svg/d2svg.go:3394

		out += ".light-code{display: block}"
		out += ".dark-code{display: none}"
	}

	return out, nil
}

func blendMode(lc string) string {
	switch lc {
	case "bright":
		return "darken"
	case "normal":
		return "color-burn"
	case "dark":
		return "overlay"
	case "darker":
		return "lighten"
	}
	panic("invalid luminance category")
}

type DiagramObject interface {
	GetID() string
	GetZIndex() int
}

// sortObjects sorts all diagrams objects (shapes and connections) in the desired drawing order
// the sorting criteria is:
// 1. zIndex, lower comes first
// 2. two shapes with the same zIndex are sorted by their level (container nesting), containers come first
// 3. two shapes with the same zIndex and same level, are sorted in the order they were exported
// 4. shape and edge, shapes come first
func sortObjects(allObjects []DiagramObject) {
	sort.SliceStable(allObjects, func(i, j int) bool {
		// first sort by zIndex
		iZIndex := allObjects[i].GetZIndex()
		jZIndex := allObjects[j].GetZIndex()

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Fix the custom theme's color values so the computed luminance falls into a supported category
  2. Only use known luminance category values when constructing themes programmatically
  3. Patch blendMode to handle the new category if you intentionally introduced one
  4. Test custom themes through d2svg Render before shipping; fall back to the default theme if rendering panics

Example fix

// before
theme.Color.N7 = "#12345678" // 8-digit hex yields unknown luminance
// after
theme.Color.N7 = "#123456" // standard hex in supported range
Defensive patterns

Strategy: validation

Validate before calling

// Validate custom theme colors produce a supported luminance category before Render
cat := luminanceToCategory(theme.Color.N7)
switch cat {
case "bright", "normal", "dark", "darker":
default:
    return fmt.Errorf("theme color %q yields invalid luminance category %q", theme.Color.N7, cat)
}

Type guard

func validLuminanceCategory(lc string) bool {
    switch lc {
    case "bright", "normal", "dark", "darker":
        return true
    }
    return false
}

Try / catch

// Recover around Render when using custom themes
defer func() {
    if r := recover(); r != nil {
        if s, ok := r.(string); ok && s == "invalid luminance category" {
            err = fmt.Errorf("theme has invalid luminance category")
        } else { panic(r) }
    }
}()

Prevention

When it happens

Trigger: Rendering with an inline/custom theme whose color luminance category (derived from theme color fields like N7 via luminance helpers) is not one of "bright"/"normal"/"dark"/"darker" when d2svg applies sketch streak overlays.

Common situations: Hand-written custom theme JSON with colors yielding an unexpected luminance bucket; theme fields set to arbitrary strings in programmatic theme construction; theme version drift where a new category was added but blendMode not updated.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/b26e4647f5c8d53c. Report an issue: GitHub.