skill-creator: Add markdown rendering to eval viewer

Resolved 💬 3 comments Opened Mar 11, 2026 by NateThom Closed Apr 11, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

The eval viewer currently renders .md files as plain < pre > text. When evaluating skills that produce markdown (Obsidian pages, docs, READMEs), it's much easier to review rendered output. This patch adds rendered markdown preview
with a Raw/Rendered toggle, using marked.js + DOMPurify for safe HTML generation.

Proposed Solution

Feature: Rendered markdown preview in eval viewer
============================================================

This patch adds markdown rendering support to the skill-creator eval viewer.
Markdown (.md) files are now rendered with proper formatting by default,
with a Rendered/Raw toggle button to switch between views.

Dependencies added (CDN):

  • marked.js — lightweight markdown parser
  • DOMPurify — HTML sanitizer to prevent XSS from parsed markdown

File: eval-viewer/viewer.html
------------------------------------------------------------

  1. Add CDN script tags after the SheetJS import:
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
  1. Add CSS (after .output-file-content .download-link:hover block):
    .output-file-header .md-toggle {
      font-size: 0.7rem;
      color: var(--accent);
      cursor: pointer;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      font-weight: 500;
      opacity: 0.8;
      background: none;
      border: 1px solid var(--accent);
      border-radius: 3px;
      padding: 0.15rem 0.4rem;
      margin-right: 0.5rem;
    }
    .output-file-header .md-toggle:hover {
      opacity: 1;
      background: var(--accent);
      color: white;
    }
    .output-file-header .md-toggle.active {
      background: var(--accent);
      color: white;
      opacity: 1;
    }
    .md-rendered {
      font-size: 0.875rem;
      line-height: 1.6;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    }
    .md-rendered h1 { font-size: 1.4rem; margin: 1rem 0 0.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
    .md-rendered h2 { font-size: 1.15rem; margin: 0.8rem 0 0.4rem; }
    .md-rendered h3 { font-size: 1rem; margin: 0.6rem 0 0.3rem; }
    .md-rendered p { margin: 0.4rem 0; }
    .md-rendered ul, .md-rendered ol { margin: 0.4rem 0 0.4rem 1.5rem; }
    .md-rendered li { margin: 0.2rem 0; }
    .md-rendered code { background: var(--bg); padding: 0.1rem 0.3rem; border-radius: 3px; font-size: 0.8rem; font-family: 'SF Mono', Consolas, monospace; }
    .md-rendered pre { background: var(--bg); padding: 0.75rem; border-radius: 4px; overflow-x: auto; margin: 0.5rem 0; }
    .md-rendered pre code { background: none; padding: 0; }
    .md-rendered a { color: var(--accent); }
    .md-rendered blockquote { border-left: 3px solid var(--border); padding-left: 0.75rem; color: var(--text-muted); margin: 0.5rem 0; }
    .md-rendered table { border-collapse: collapse; font-size: 0.8125rem; width: 100%; margin: 0.5rem 0; }
    .md-rendered table td, .md-rendered table th { border: 1px solid var(--border); padding: 0.375rem 0.5rem; text-align: left; }
    .md-rendered table th { background: var(--bg); font-weight: 600; }
    .md-rendered hr { border: none; border-top: 1px solid var(--border); margin: 0.75rem 0; }
    .md-rendered strong { font-weight: 600; }
  1. Add JavaScript helper function (before the XLSX rendering section):
    // ---- Markdown rendering with raw/rendered toggle ----
    function renderMarkdownWithToggle(container, mdText, toggleBtn) {
      const renderedDiv = document.createElement("div");
      renderedDiv.className = "md-rendered";
      const rawHtml = marked.parse(mdText);
      const cleanHtml = typeof DOMPurify !== "undefined" ? DOMPurify.sanitize(rawHtml) : mdText;
      renderedDiv.appendChild(document.createRange().createContextualFragment(cleanHtml));
      container.appendChild(renderedDiv);

      const rawPre = document.createElement("pre");
      rawPre.textContent = mdText;
      rawPre.style.display = "none";
      container.appendChild(rawPre);

      if (toggleBtn) {
        toggleBtn.addEventListener("click", function() {
          if (this.dataset.mode === "rendered") {
            renderedDiv.style.display = "none";
            rawPre.style.display = "";
            this.textContent = "Raw";
            this.dataset.mode = "raw";
            this.classList.remove("active");
          } else {
            renderedDiv.style.display = "";
            rawPre.style.display = "none";
            this.textContent = "Rendered";
            this.dataset.mode = "rendered";
            this.classList.add("active");
          }
        });
      }
    }
  1. Modify renderOutputs() — replace the file header + text rendering block:

In both the main renderOutputs function and the previous-outputs rendering
section, wrap the download button in a headerActions span that also includes
a toggle button for .md files, then use renderMarkdownWithToggle instead of
a plain <pre> tag when the file is markdown:

        // Detect markdown files
        const isMd = file.type === "text" && file.name.endsWith(".md") && typeof marked !== "undefined";

        // Add toggle button to header for .md files
        if (isMd) {
          const toggleBtn = document.createElement("button");
          toggleBtn.className = "md-toggle active";
          toggleBtn.textContent = "Rendered";
          toggleBtn.dataset.mode = "rendered";
          headerActions.appendChild(toggleBtn);
        }

        // In the content rendering block:
        if (file.type === "text") {
          if (isMd) {
            renderMarkdownWithToggle(content, file.content, header.querySelector(".md-toggle"));
          } else {
            const pre = document.createElement("pre");
            pre.textContent = file.content;
            content.appendChild(pre);
          }
        }

This pattern is applied in two locations: the main renderOutputs() function
and the previous-outputs collapsible section.

Alternative Solutions

_No response_

Priority

Medium - Would be very helpful

Feature Category

CLI commands and flags

Use Case Example

Evaluating skills that produce markdown (Obsidian pages, docs, READMEs), it's much easier to review rendered output.

Additional Context

_No response_

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗