{"record":{"id":"1c43a06e1502f130","repo":"siyuan-note/siyuan","slug":"image-layer-cannot-declare-a-poster","errorCode":null,"errorMessage":"image layer cannot declare a poster","messagePattern":"image layer cannot declare a poster","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":476,"sourceCode":"\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}\n\t\t} else if layer.Poster != \"\" {\n\t\t\terr = errors.New(\"image layer cannot declare a poster\")\n\t\t\treturn nil, err\n\t\t}\n\t\tfit := layer.Fit\n\t\tif fit == \"\" {\n\t\t\tfit = \"cover\"\n\t\t}\n\t\tif !isValidBootAppearanceFit(fit) {\n\t\t\terr = fmt.Errorf(\"invalid layer fit [%s]\", fit)\n\t\t\treturn nil, err\n\t\t}\n\t\tposition := layer.Position\n\t\tif position == \"\" {\n\t\t\tposition = \"center\"\n\t\t}\n\t\tif !isValidBootAppearancePosition(position) {\n\t\t\terr = fmt.Errorf(\"invalid layer position [%s]\", position)\n\t\t\treturn nil, err\n\t\t}","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L458-L494","documentation":"Poster is only meaningful for video layers. If an \"image\" layer sets a non-empty poster field, loadBootAppearance rejects the manifest, because images render directly and have no pre-load placeholder.","triggerScenarios":"GetBootAppearances/getBootAppearanceByID loading a manifest.json where a layer with \"type\": \"image\" (or a non-video type) declares a poster value. Typically happens after switching a layer's src from video to image while keeping the poster field, or copy-pasting a video layer object.","commonSituations":"Author replaces an animated video layer with a static image but leaves \"poster\" in the JSON; a manifest template that always includes poster for every layer; bulk-generated manifests that emit poster unconditionally.","solutions":["Remove the poster field (or set it to \"\") from every image-type layer in manifest.json","Keep the poster field only on layers whose type is \"video\"","If you intended a video, change the layer type back to \"video\" and keep src as an .mp4 with the poster retained"],"exampleFix":"// before\n{\"id\": \"bg\", \"type\": \"image\", \"src\": \"bg.png\", \"poster\": \"bg-thumb.png\"}\n// after\n{\"id\": \"bg\", \"type\": \"image\", \"src\": \"bg.png\"}","handlingStrategy":"validation","validationCode":"manifest.layers?.forEach(l => {\n  if (l.type !== \"video\" && l.poster) throw new Error(`image layer ${l.id} must not declare poster`);\n});","typeGuard":"type Layer = { type: \"image\"; src: string; poster?: never } | { type: \"video\"; src: string; poster: string };","tryCatchPattern":null,"preventionTips":["Delete the poster key when converting a video layer to an image layer","Use discriminated union types for layers so poster is only allowed on video","Lint manifests to catch leftover poster fields","When copy-pasting layer objects, strip inapplicable fields"],"tags":["validation","manifest","plugin","mutually-exclusive-fields"],"backgroundTag":"conflicting-config-options","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}