{"record":{"id":"b16b6fc4fd022500","repo":"siyuan-note/siyuan","slug":"duplicate-inline-style-id-s","errorCode":null,"errorMessage":"duplicate inline style ID [%s]","messagePattern":"duplicate inline style ID \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/inline_style.go","lineNumber":667,"sourceCode":"\tfor _, style := range styles {\n\t\tif style == nil {\n\t\t\treturn nil, errors.New(\"inline style must not be null\")\n\t\t}\n\n\t\tid := strings.TrimSpace(style.ID)\n\t\tif id == \"\" && generateIDs {\n\t\t\tfor {\n\t\t\t\tid = ast.NewNodeID()\n\t\t\t\tif _, exists := ids[id]; !exists {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !ast.IsNodeIDPattern(id) {\n\t\t\treturn nil, fmt.Errorf(\"invalid inline style ID [%s]\", id)\n\t\t}\n\t\tif _, exists := ids[id]; exists {\n\t\t\treturn nil, fmt.Errorf(\"duplicate inline style ID [%s]\", id)\n\t\t}\n\t\tids[id] = struct{}{}\n\n\t\tname := strings.TrimSpace(style.Name)\n\t\tif name == \"\" {\n\t\t\treturn nil, errors.New(\"inline style name must not be empty\")\n\t\t}\n\t\tif maxInlineStyleNameRunes < utf8.RuneCountInString(name) {\n\t\t\treturn nil, fmt.Errorf(\"inline style name exceeds the %d character limit\", maxInlineStyleNameRunes)\n\t\t}\n\t\tif style.Light == nil || style.Dark == nil {\n\t\t\treturn nil, fmt.Errorf(\"inline style [%s] must define light and dark themes\", id)\n\t\t}\n\n\t\tlight, err := normalizeInlineStyleTheme(style.Light)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid light theme of inline style [%s]: %w\", id, err)\n\t\t}","sourceCodeStart":649,"sourceCodeEnd":685,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/inline_style.go#L649-L685","documentation":"Inline style IDs must be unique within the workspace style list because the editor references styles by ID. normalizeInlineStyles tracks seen IDs and aborts the entire save/load when two entries share the same ID, preventing ambiguous style resolution.","triggerScenarios":"setInlineStylesData receives a list where two or more InlineStyle entries have the same (trimmed) ID; loadInlineStyles reads a config file containing duplicated IDs, e.g. from a copy-paste or a bad merge/sync of the inline-styles JSON.","commonSituations":"Duplicating an existing custom style by copying its JSON entry without changing the ID; concurrent clients writing style lists that both contain the same generated ID; restoring a merged config file.","solutions":["Reassign one of the duplicates a fresh unique node-ID-format ID before saving.","Deduplicate the list before the API call (keep one entry per ID).","Edit the on-disk inline-styles JSON and change or drop the duplicated entry so loadInlineStyles succeeds."],"exampleFix":"// before\nstyles := []*InlineStyle{\n  {ID: \"20240101120000-abcdefg\", Name: \"Red\"},\n  {ID: \"20240101120000-abcdefg\", Name: \"Red copy\"},\n}\n// after\nstyles := []*InlineStyle{\n  {ID: \"20240101120000-abcdefg\", Name: \"Red\"},\n  {ID: \"20240101120100-ghijklm\", Name: \"Red copy\"},\n}","handlingStrategy":"validation","validationCode":"const ids = styles.map(s => s.ID.trim()); if (new Set(ids).size !== ids.length) throw new Error('duplicate style IDs');","typeGuard":null,"tryCatchPattern":"try { await saveStyles(styles) } catch (e) { if (/duplicate inline style ID/.test(String(e))) { /* deduplicate or regenerate the duplicated ID, then retry */ } }","preventionTips":["Deduplicate by ID before every save","When cloning a style, always mint a new ID","Check merges/synced config files for duplicate entries"],"tags":["validation","uniqueness","inline-styles","duplicate"],"backgroundTag":"schema-validation-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}