{"record":{"id":"e43fd62720525bb0","repo":"siyuan-note/siyuan","slug":"invalid-or-duplicate-layer-id","errorCode":null,"errorMessage":"invalid or duplicate layer ID","messagePattern":"invalid or duplicate layer ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":456,"sourceCode":"\t\tif err = validateOptionalBootAppearanceColor(color); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tret.OfficialUI.TextColor = manifest.OfficialUI.TextColor\n\tret.OfficialUI.ProgressColor = manifest.OfficialUI.ProgressColor\n\tret.OfficialUI.TrackColor = manifest.OfficialUI.TrackColor\n\n\tif manifest.Style != \"\" {\n\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, manifest.Style, \"style\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid style: %w\", err)\n\t\t}\n\t\tret.Style = bootAppearanceAssetURL(pkg.Name, appearanceID, manifest.Style)\n\t}\n\n\tlayerIDs := map[string]bool{}\n\tfor _, layer := range manifest.Layers {\n\t\tif layer == nil || !isValidBootAppearanceID(layer.ID) || layerIDs[layer.ID] {\n\t\t\terr = errors.New(\"invalid or duplicate layer ID\")\n\t\t\treturn nil, err\n\t\t}\n\t\tlayerIDs[layer.ID] = true\n\t\tif layer.Type != \"image\" && layer.Type != \"video\" {\n\t\t\terr = fmt.Errorf(\"unsupported layer type [%s]\", layer.Type)\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Src, layer.Type); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid layer source: %w\", err)\n\t\t}\n\t\tif layer.Type == \"video\" {\n\t\t\tif layer.Poster == \"\" {\n\t\t\t\terr = errors.New(\"video poster is required\")\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Poster, \"image\"); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid video poster: %w\", err)\n\t\t\t}","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L438-L474","documentation":"Each manifest layer must have a non-nil object, an ID that passes isValidBootAppearanceID (pattern + <=64 chars), and an ID unique within the layers array. loadBootAppearance aborts otherwise, since layer IDs key the layer list used by the frontend.","triggerScenarios":"GetBootAppearances/getBootAppearanceByID iterating manifest.Layers when a layer entry is null, has an empty/invalid ID (spaces, slashes, non-ASCII, >64 chars), or duplicates an ID already seen in the same layers array.","commonSituations":"Copy-pasting a layer JSON object and forgetting to change its id; a template left 'id': '' or 'id': 'layer 1' with a space; a generator emitting null entries for optional layers; two layers both named 'logo'.","solutions":["Give every layer in manifest.json a unique, pattern-valid ID (slug form, <=64 chars), e.g. 'background', 'logo'","Remove null layer entries from the layers array","Rename duplicated IDs so each is unique within the array","Update the manifest's schemaVersion/id checks only after fixing IDs so the load can proceed"],"exampleFix":"// before\n\"layers\": [\n  {\"id\": \"bg\", \"type\": \"image\", \"src\": \"bg.png\"},\n  {\"id\": \"bg\", \"type\": \"image\", \"src\": \"bg2.png\"}\n]\n// after\n\"layers\": [\n  {\"id\": \"bg\", \"type\": \"image\", \"src\": \"bg.png\"},\n  {\"id\": \"bg-alt\", \"type\": \"image\", \"src\": \"bg2.png\"}\n]","handlingStrategy":"validation","validationCode":"const seen = new Set();\nmanifest.layers?.forEach((l, i) => {\n  if (!l || typeof l.id !== \"string\" || l.id.length > 64 || !/^[a-z0-9][a-z0-9-]*$/.test(l.id)) throw new Error(`layer ${i}: invalid id`);\n  if (seen.has(l.id)) throw new Error(`duplicate layer id ${l.id}`);\n  seen.add(l.id);\n});","typeGuard":"const hasValidLayerId = (l: unknown): l is { id: string } =>\n  !!l && typeof (l as any).id === \"string\" && (l as any).id.length <= 64 && /^[a-z0-9][a-z0-9-]*$/.test((l as any).id);","tryCatchPattern":null,"preventionTips":["Make layer IDs unique per appearance; suffix copies ('logo', 'logo-2')","Run a manifest lint step in the plugin build pipeline","Never emit null layer entries from generators","Keep IDs slug-formatted and <= 64 chars"],"tags":["validation","duplicate","plugin","manifest"],"backgroundTag":"invalid-identifier-format","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"}