siyuan-note/siyuan · error

duplicated attribute view custom color index [%d]

Error message

duplicated attribute view custom color index [%d]

What it means

NormalizeAttributeViewCustomColors returns this error in strict mode when two custom colors share the same Index. Each custom color index is a unique slot; duplicates are ambiguous for rendering, so strict mode rejects them while non-strict mode silently drops later duplicates.

Source

Thrown at kernel/av/color.go:110

// NormalizeAttributeViewCustomColors 校验并规范化数据库自定义颜色。
func NormalizeAttributeViewCustomColors(colors []*AttributeViewCustomColor, strict bool) (ret []*AttributeViewCustomColor, err error) {
	if strict && MaxCustomColors < len(colors) {
		return nil, fmt.Errorf("attribute view custom colors count exceeds the %d item limit", MaxCustomColors)
	}

	indexes := map[int]struct{}{}
	for _, color := range colors {
		normalized, normalizeErr := normalizeAttributeViewCustomColor(color)
		if nil != normalizeErr {
			if strict {
				return nil, normalizeErr
			}
			continue
		}
		if _, ok := indexes[normalized.Index]; ok {
			if strict {
				return nil, fmt.Errorf("duplicated attribute view custom color index [%d]", normalized.Index)
			}
			continue
		}
		indexes[normalized.Index] = struct{}{}
		ret = append(ret, normalized)
	}

	sort.Slice(ret, func(i, j int) bool {
		return ret[i].Index < ret[j].Index
	})
	if nil == ret {
		ret = []*AttributeViewCustomColor{}
	}
	return
}

func normalizeAttributeViewCustomColor(color *AttributeViewCustomColor) (ret *AttributeViewCustomColor, err error) {
	if nil == color {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Reassign duplicate entries with av.NextCustomColorIndex (or the next free index from UsedCustomColorIndexes) before normalizing.
  2. Dedupe by index first: keep one entry per index (merge Hidden/Light/Dark) and drop or merge the rest.
  3. Call NormalizeAttributeViewCustomColors with strict=false to silently drop duplicates if lossy handling is acceptable.
  4. When generating colors programmatically, always allocate via NextCustomColorIndex instead of hardcoded indexes.

Example fix

// before
colors := []*av.AttributeViewCustomColor{{Index: 15, ...}, {Index: 15, ...}} // duplicate
ret, err := av.NormalizeAttributeViewCustomColors(colors, true)

// after
colors[1].Index = av.NextCustomColorIndex(attrView)
ret, err := av.NormalizeAttributeViewCustomColors(colors, true)
Defensive patterns

Strategy: validation

Validate before calling

func hasDuplicateIndexes(colors []*av.AttributeViewCustomColor) bool {
	seen := map[int]struct{}{}
	for _, c := range colors {
		if _, ok := seen[c.Index]; ok { return true }
		seen[c.Index] = struct{}{}
	}
	return false
}

Try / catch

if _, err := av.NormalizeAttributeViewCustomColors(colors, true); err != nil && strings.Contains(err.Error(), "duplicated") {
	// reassign with av.NextCustomColorIndex and retry
}

Prevention

When it happens

Trigger: Calling av.NormalizeAttributeViewCustomColors(colors, true) where two entries carry the same Index (color.go:108); decoding a palette (decodeHistoricalWorkspacePalette) or applying colors via setAttrViewCustomColors with duplicated indexes.

Common situations: Merging two attribute views' color definitions without reindexing; a script appending new colors reusing an existing index instead of av.NextCustomColorIndex; hand-edited .av JSON with copy-pasted color blocks.

Related errors


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