siyuan-note/siyuan · error

%s must use the same fields in light and dark themes

Error message

%s must use the same fields in light and dark themes

What it means

SiYuan inline-style color themes are stored as a paired light/dark theme, and both halves must fill in exactly the same set of fields. After normalization the code computes booleans for whether Color and BackgroundColor are present in each half; if the presence pattern differs between light and dark (e.g. light sets only Color, dark sets only BackgroundColor), the theme is rejected. This keeps rendering consistent when the user switches appearance mode.

Source

Thrown at kernel/model/inline_style.go:807

func normalizeInlineStyleThemePair(light, dark *InlineStyleTheme, description string) (normalizedLight,
	normalizedDark *InlineStyleTheme, err error) {
	if light == nil || dark == nil {
		return nil, nil, fmt.Errorf("%s must define light and dark themes", description)
	}
	if normalizedLight, err = normalizeInlineStyleTheme(light); err != nil {
		return nil, nil, fmt.Errorf("invalid light theme of %s: %w", description, err)
	}
	if normalizedDark, err = normalizeInlineStyleTheme(dark); err != nil {
		return nil, nil, fmt.Errorf("invalid dark theme of %s: %w", description, err)
	}
	lightColor, lightBackground := normalizedLight.Color != "", normalizedLight.BackgroundColor != ""
	darkColor, darkBackground := normalizedDark.Color != "", normalizedDark.BackgroundColor != ""
	if !lightColor && !lightBackground {
		return nil, nil, fmt.Errorf("%s must define color or backgroundColor", description)
	}
	if lightColor != darkColor || lightBackground != darkBackground {
		return nil, nil, fmt.Errorf("%s must use the same fields in light and dark themes", description)
	}
	return normalizedLight, normalizedDark, nil
}

func normalizeHiddenBuiltinColorIndexes(indexes []int, field string) (ret []int, err error) {
	ret = make([]int, 0, len(indexes))
	seen := make(map[int]struct{}, len(indexes))
	maxIndex := maxBuiltinColorIndex
	if field == "av" {
		maxIndex = neutralAVColorIndex
	}
	for _, index := range indexes {
		if index < minBuiltinColorIndex || maxIndex < index {
			return nil, fmt.Errorf("hidden builtin %s index [%d] must be between %d and %d", field, index,
				minBuiltinColorIndex, maxIndex)
		}
		if _, exists := seen[index]; exists {
			continue

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Ensure the dark theme defines exactly the same fields (color and/or backgroundColor) as the light theme.
  2. If a field should be empty in dark mode, explicitly set it to "" in both halves rather than omitting it in only one.
  3. Generate both theme halves from the same struct/template so their field sets cannot diverge.

Example fix

// before
theme := &InlineStyleTheme{
  Light: &InlineStyleThemeHalf{Color: "#ff0000"},
  Dark:  &InlineStyleThemeHalf{BackgroundColor: "#003300"}, // mismatched fields
}
// after
theme := &InlineStyleTheme{
  Light: &InlineStyleThemeHalf{Color: "#ff0000", BackgroundColor: ""},
  Dark:  &InlineStyleThemeHalf{Color: "#00ff00", BackgroundColor: ""}, // same fields
}
Defensive patterns

Strategy: validation

Validate before calling

function themeFieldsMatch(light, dark) {
  const has = (t, k) => Boolean(t && typeof t[k] === "string" && t[k] !== "");
  return has(light, "color") === has(dark, "color") &&
         has(light, "backgroundColor") === has(dark, "backgroundColor");
}

Prevention

When it happens

Trigger: Calling any API that saves a custom inline style theme (normalizeInlineStyleThemes path in kernel/model/inline_style.go) with a light theme defining {color} and a dark theme defining {backgroundColor}, or one half empty while the other defines a field, or one half defining both fields and the other only one.

Common situations: Developers hand-crafting theme JSON for plugin/UI customization, migrating old single-theme configs to the paired format, or programmatically generating themes where the dark variant was built from a different template than the light variant.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/e89e2d72018ed01f. Report an issue: GitHub.