{"record":{"id":"c01d8dd92f2094c3","repo":"siyuan-note/siyuan","slug":"name-must-end-in-html-or-htm","errorCode":null,"errorMessage":"name must end in .html or .htm","messagePattern":"name must end in \\.html or \\.htm","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/asset.go","lineNumber":176,"sourceCode":"\t\tContent: []ContentItem{{Type: \"text\", Text: fmt.Sprintf(\"Created HTML IFrame block: %s\\nAsset: %s\", blockID, assetPath)}},\n\t\tStructuredContent: map[string]any{\n\t\t\t\"blockID\":   blockID,\n\t\t\t\"assetPath\": assetPath,\n\t\t},\n\t\tStructuredContentSet: true,\n\t}, nil\n}\n\nfunc normalizeHTMLAssetName(value any) (string, error) {\n\tname, _ := value.(string)\n\tname = strings.TrimSpace(name)\n\tif name == \"\" {\n\t\tname = \"component.html\"\n\t}\n\tname = filepath.Base(name)\n\text := strings.ToLower(filepath.Ext(name))\n\tif ext != \".html\" && ext != \".htm\" {\n\t\treturn \"\", fmt.Errorf(\"name must end in .html or .htm\")\n\t}\n\treturn name, nil\n}\n\nfunc htmlAssetIFrameBlockDOM(assetPath string) (dom, blockID string, err error) {\n\tsrc := html.EscapeString(model.HTMLAssetIFrameSrc(assetPath))\n\tdom, err = markdownToBlockDOM(`<iframe sandbox=\"allow-scripts\" src=\"` + src + `\" border=\"0\" frameborder=\"no\" framespacing=\"0\" allowfullscreen=\"true\"></iframe>`)\n\tif err != nil {\n\t\treturn\n\t}\n\ttree := util.NewLute().BlockDOM2Tree(dom)\n\tif tree == nil || tree.Root == nil || tree.Root.FirstChild == nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"empty IFrame block\")\n\t}\n\tblockID = tree.Root.FirstChild.ID\n\treturn\n}\n","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/tools/asset.go#L158-L194","documentation":"`normalizeHTMLAssetName` requires the supplied asset file name to end in `.html` or `.htm` (case-insensitive). The HTML asset feature stores a single HTML file that gets embedded via an `<iframe>`, so only HTML extensions are accepted; any other extension is rejected before the file is written.","triggerScenarios":"Calling the HTML-asset MCP tool with a `name` argument whose extension (after `filepath.Base` and lowercasing) is neither `.html` nor `.htm` — e.g. `.txt`, `.svg`, `.js`, or no extension at all.","commonSituations":"Passing a full path whose final component has a different extension. Providing a component name like `widget.js` when an HTML wrapper is required. Forgetting the extension entirely.","solutions":["Supply a `name` ending in `.html` (or `.htm`), e.g. `component.html`.","If `name` is omitted it defaults to `component.html`; only override it with another `.html`/`.htm` value.","Remember only `filepath.Base` is kept, so directories in the value are ignored — set the extension on the filename itself."],"exampleFix":"// before\n{\"name\": \"widget.js\", \"content\": \"...\"}\n// after\n{\"name\": \"widget.html\", \"content\": \"...\"}","handlingStrategy":"validation","validationCode":"// Validate the asset name extension before calling the tool.\nfunc validHTMLName(name string) bool {\n    ext := strings.ToLower(filepath.Ext(strings.TrimSpace(name)))\n    return ext == \".html\" || ext == \".htm\"\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to `component.html` when unsure.","Set the extension on the filename itself (directories are stripped by filepath.Base).","Reject non-.html/.htm names client-side before invoking the tool."],"tags":["mcp","asset","html","input-validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}