{"record":{"id":"6adc41efed7036e9","repo":"siyuan-note/siyuan","slug":"invalid-appearance-id","errorCode":null,"errorMessage":"invalid appearance ID","messagePattern":"invalid appearance ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":377,"sourceCode":"\tif err != nil {\n\t\treturn\n\t}\n\tcurrent := defaultBootAppearanceSelection()\n\tif err = gulu.JSON.UnmarshalJSON(data, &current); err != nil || current != expected {\n\t\treturn\n\t}\n\tdata, err = gulu.JSON.MarshalIndentJSON(defaultBootAppearanceSelection(), \"\", \"\\t\")\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = filelock.WriteFile(configPath, data); err != nil {\n\t\tlogging.LogWarnf(\"clear invalid boot appearance selection failed: %s\", err)\n\t}\n}\n\nfunc loadBootAppearance(pluginDir string, pkg *bazaar.Package, appearanceID string) (ret *BootAppearance, err error) {\n\tif !isValidBootAppearanceID(appearanceID) {\n\t\terr = errors.New(\"invalid appearance ID\")\n\t\treturn\n\t}\n\tappearanceDir := filepath.Join(pluginDir, bootAppearanceDirName, appearanceID)\n\tif err = validateBootAppearancePackage(pluginDir, appearanceDir); err != nil {\n\t\treturn\n\t}\n\tmanifestPath, _, resolveErr := validateBootAppearanceResource(pluginDir, appearanceDir, bootAppearanceManifestName, \"manifest\")\n\tif resolveErr != nil {\n\t\terr = resolveErr\n\t\treturn\n\t}\n\tdata, readErr := filelock.ReadFile(manifestPath)\n\tif readErr != nil {\n\t\terr = readErr\n\t\treturn\n\t}\n\tmanifest := &bootAppearanceManifest{}\n\tif err = gulu.JSON.UnmarshalJSON(data, manifest); err != nil {","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/boot_appearance.go#L359-L395","documentation":"loadBootAppearance validates the appearance ID before attempting to read the appearance's directory under the plugin. An ID that doesn't match the strict kebab-case pattern ^[a-z0-9]+(-[a-z0-9]+)*$ is rejected immediately with 'invalid appearance ID', preventing path/lookup issues from malformed names.","triggerScenarios":"getBootAppearanceByID or GetBootAppearances reaching loadBootAppearance with an appearance ID containing uppercase letters, underscores, spaces, slashes, empty string, or repeated/leading/trailing hyphens.","commonSituations":"Directory names inside the plugin's appearance folder that aren't kebab-case (e.g. 'MyTheme', 'old_theme'); IDs derived from user input or filenames; plugin packages shipped with non-conforming appearance folder names.","solutions":["Rename the appearance directory inside the plugin to lowercase kebab-case (e.g. my-appearance)","Validate the ID against ^[a-z0-9]+(-[a-z0-9]+)*$ before calling","Get a valid ID from GetBootAppearances rather than constructing it from the filesystem or user input"],"exampleFix":"// before\napp, err := model.GetBootAppearanceByID(\"plugin\", \"Dark Theme\")\n// after\napp, err := model.GetBootAppearanceByID(\"plugin\", \"dark-theme\")","handlingStrategy":"validation","validationCode":"var idRe = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)\nif !idRe.MatchString(appearanceID) { return errors.New(\"invalid appearance ID\") }","typeGuard":"func isValidAppearanceID(id string) bool { return regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`).MatchString(id) }","tryCatchPattern":"app, err := loadBootAppearance(dir, pkg, id)\nif err != nil && strings.Contains(err.Error(), \"invalid appearance ID\") {\n  // skip or normalize the ID and retry\n}","preventionTips":["Name appearance directories in strict lowercase kebab-case","Derive IDs from GetBootAppearances listings, not raw filenames","Normalize user input to kebab-case before lookup"],"tags":["go","kernel","identifier","validation","boot-appearance"],"backgroundTag":"invalid-identifier-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}