{"record":{"id":"d10d57d311a1610f","repo":"siyuan-note/siyuan","slug":"no-supported-frontend","errorCode":null,"errorMessage":"no supported frontend","messagePattern":"no supported frontend","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":728,"sourceCode":"\t\t\tnormalized = \"desktop\"\n\t\tcase \"mobile\":\n\t\t\tnormalized = \"mobile\"\n\t\tdefault:\n\t\t\tif len(manifestFrontends) > 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported frontend [%s]\", frontend)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif !seen[normalized] {\n\t\t\tseen[normalized] = true\n\t\t\tfrontends = append(frontends, normalized)\n\t\t}\n\t}\n\tif len(frontends) == 0 && len(manifestFrontends) == 0 && len(pluginFrontends) == 0 {\n\t\tfrontends = append(frontends, \"desktop\", \"mobile\")\n\t}\n\tif len(frontends) == 0 {\n\t\treturn nil, errors.New(\"no supported frontend\")\n\t}\n\treturn frontends, nil\n}\n\nfunc validateOptionalBootAppearanceColor(color string) error {\n\tif color != \"\" && !bootAppearanceColorPattern.MatchString(color) {\n\t\treturn fmt.Errorf(\"invalid color [%s]\", color)\n\t}\n\treturn nil\n}\n\nfunc isValidBootAppearanceID(id string) bool {\n\treturn len(id) <= 64 && bootAppearanceIDPattern.MatchString(id)\n}\n\nfunc isSafeBootAppearanceRelativePath(relativePath string) bool {\n\tif relativePath == \"\" || len(relativePath) > maxBootAppearancePathLength || strings.Contains(relativePath, \"\\\\\") ||\n\t\tstrings.ContainsAny(relativePath, `<>:\"|?*`) || strings.HasPrefix(relativePath, \"/\") ||","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L710-L746","documentation":"Thrown by normalizeBootAppearanceFrontends when after normalization no usable frontend remains: declared frontends were all invalid, or plugin frontends filtered everything out while manifestFrontends was empty. The default [desktop, mobile] is only applied when both input lists are completely empty.","triggerScenarios":"Manifest omits frontends (so the manifest defaults do not run) but the plugin's frontends list excludes both desktop and mobile; or an empty-but-present combination leaves len(frontends)==0 with non-empty pluginFrontends.","commonSituations":"A plugin package targeting only an unsupported frontend, or configuration drift between the manifest and the plugin's declared frontends.","solutions":["Set manifest frontends to [\"desktop\"], [\"mobile\"], or [\"desktop\",\"mobile\"].","If using plugin frontends, ensure they include at least desktop or mobile.","Remove the conflicting plugin frontend declarations so the defaults apply."],"exampleFix":"// before\n\"frontends\": []  // plugin declares only unsupported frontends\n// after\n\"frontends\": [\"desktop\", \"mobile\"]","handlingStrategy":"validation","validationCode":"function resolvesToFrontend(manifestF, pluginF) { const has = a => (a || []).some(f => f === \"desktop\" || f === \"mobile\"); return has(manifestF) ? has(manifestF) : (has(pluginF) || ((manifestF || []).length === 0 && (pluginF || []).length === 0)); }","typeGuard":null,"tryCatchPattern":"try { await installBootAppearance(pkg); } catch (e) { if (String(e).includes(\"no supported frontend\")) { /* declare desktop/mobile in manifest frontends */ } else throw e; }","preventionTips":["Always declare frontends explicitly in the manifest","Ensure plugin frontend lists intersect with desktop/mobile","Test package load before distribution"],"tags":["validation","manifest","frontend"],"backgroundTag":"empty-required-field","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"}