Fix skills detail markdown styling with preview-md wrapper

Skill detail and linked markdown files now use the same preview-md
pipeline as Memory/Notes, with code highlighting and KaTeX enhancement.
This commit is contained in:
Pamnard
2026-06-01 00:28:51 +03:00
committed by nesquena-hermes
parent e16f699313
commit f24d633189
3 changed files with 75 additions and 3 deletions

View File

@@ -3,6 +3,9 @@
## [Unreleased]
### Fixed
- Skills detail view now renders `SKILL.md` markdown with the same `.preview-md` typography used by Memory and Notes, instead of unstyled `renderMd()` output. Linked markdown skill files use the same wrapper and post-render code/KaTeX enhancement.
## [v0.51.190] — 2026-05-31 — Release FJ (stage-batch2 — Windows upgrade state-stranding hotfix + gateway banner + quiet tool previews)
### Fixed

View File

@@ -3640,6 +3640,19 @@ function _stripYamlFrontmatter(content) {
return { frontmatter: m[1], body: content.slice(m[0].length) };
}
function _skillMarkdownHtml(markdown) {
return `<div class="preview-md">${renderMd(markdown || '')}</div>`;
}
function _enhanceSkillMarkdown(root) {
if (!root) return;
requestAnimationFrame(() => {
const mdRoot = root.querySelector('.preview-md') || root;
if (typeof highlightCode === 'function') highlightCode(mdRoot);
if (typeof renderKatexBlocks === 'function') renderKatexBlocks(mdRoot);
});
}
function _renderSkillDetail(name, content, linkedFiles) {
const title = $('skillDetailTitle');
const body = $('skillDetailBody');
@@ -3652,7 +3665,7 @@ function _renderSkillDetail(name, content, linkedFiles) {
if (frontmatter) {
html += `<details class="skill-frontmatter"><summary>${esc(t('skill_metadata'))}</summary><pre><code>${esc(frontmatter)}</code></pre></details>`;
}
html += renderMd(markdownBody || '(no content)');
html += _skillMarkdownHtml(markdownBody || '(no content)');
const lf = linkedFiles || {};
const categories = Object.entries(lf).filter(([,files]) => files && files.length > 0);
if (categories.length) {
@@ -3667,6 +3680,7 @@ function _renderSkillDetail(name, content, linkedFiles) {
html += '</div>';
}
body.innerHTML = `<div class="main-view-content skill-detail-content">${html}</div>`;
_enhanceSkillMarkdown(body);
body.querySelectorAll('.skill-linked-file').forEach(a => {
a.addEventListener('click', e => { e.preventDefault(); openSkillFile(a.dataset.skillName, a.dataset.skillFile); });
});
@@ -3738,7 +3752,7 @@ async function openSkillFile(skillName, filePath) {
const header = `<div class="skill-file-breadcrumb"><a href="#" class="skill-file-back" data-skill-name="${esc(skillName)}">&larr; ${esc(backLabel)}</a><span class="skill-file-path">${esc(filePath)}</span></div>`;
let content;
if (isMd) {
content = `<div class="main-view-content">${renderMd(data.content || '')}</div>`;
content = `<div class="main-view-content">${_skillMarkdownHtml(data.content || '')}</div>`;
} else {
const escaped = esc(data.content || '');
content = `<pre class="skill-file-code"><code>${escaped}</code></pre>`;
@@ -3757,7 +3771,8 @@ async function openSkillFile(skillName, filePath) {
}
});
});
if (!isMd) requestAnimationFrame(() => { if (typeof highlightCode === 'function') highlightCode(); });
if (isMd) _enhanceSkillMarkdown(body);
else requestAnimationFrame(() => { if (typeof highlightCode === 'function') highlightCode(); });
} catch(e) { setStatus(t('skill_file_load_failed') + e.message); }
}

View File

@@ -0,0 +1,54 @@
"""Skills detail markdown must use the shared preview-md styling pipeline."""
from pathlib import Path
PANELS_JS = Path("static/panels.js").read_text(encoding="utf-8")
def _function_block(name: str) -> str:
marker = f"function {name}("
start = PANELS_JS.find(marker)
assert start != -1, f"{name}() not found"
params_end = PANELS_JS.find("){", start)
assert params_end != -1, f"{name}() body not found"
brace = params_end + 1
depth = 0
for idx in range(brace, len(PANELS_JS)):
ch = PANELS_JS[idx]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return PANELS_JS[start : idx + 1]
raise AssertionError(f"{name}() body did not close")
def test_skill_detail_wraps_markdown_in_preview_md():
block = _function_block("_renderSkillDetail")
assert "_skillMarkdownHtml(" in block, "Skill detail must render through the shared markdown wrapper"
assert "renderMd(markdownBody" not in block.replace(" ", ""), (
"Skill detail must not inject raw renderMd() output without preview-md styling"
)
def test_skill_markdown_helper_uses_preview_md():
block = _function_block("_skillMarkdownHtml")
assert 'class="preview-md"' in block.replace("'", '"')
assert "renderMd(" in block
def test_skill_detail_enhances_markdown_after_render():
detail_block = _function_block("_renderSkillDetail")
assert "_enhanceSkillMarkdown(body)" in detail_block
enhance_block = _function_block("_enhanceSkillMarkdown")
assert "highlightCode" in enhance_block
assert "renderKatexBlocks" in enhance_block
def test_open_skill_file_markdown_uses_preview_md():
block = _function_block("openSkillFile")
assert "_skillMarkdownHtml(" in block
assert "if (isMd) _enhanceSkillMarkdown(body)" in block