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:
@@ -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
|
||||
|
||||
@@ -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)}">← ${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); }
|
||||
}
|
||||
|
||||
|
||||
54
tests/test_skill_detail_markdown_styling.py
Normal file
54
tests/test_skill_detail_markdown_styling.py
Normal 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
|
||||
Reference in New Issue
Block a user