Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Recover gracefully from mermaid render errors
Rūdolfs Ošiņš committed 13 days ago
commit 6fb702f886bcd15e1dd262ff25c6313ea88e57a3
parent 9ebc5f6b3bcd71df03b781498b2bd68659687f32
3 files changed +43 -20
modified src/components/Markdown.svelte
@@ -10,6 +10,9 @@
        mermaid.initialize({
          startOnLoad: false,
          securityLevel: "strict",
+
          // Don't let mermaid inject its own "Syntax error" bomb into the
+
          // document; we render a custom warning instead.
+
          suppressErrorRendering: true,
        });
        return mermaid;
      });
@@ -254,31 +257,36 @@

      const language = className.slice(prefix.length);

-
      // Handle mermaid diagrams
      if (language === "mermaid") {
        const mermaid = await loadMermaid();
-
        const mermaidCode = node.textContent || "";
+
        const diagramId = `mermaid-${crypto.randomUUID()}`;
        try {
-
          // First, validate the syntax without rendering
-
          const isValid = await mermaid.parse(mermaidCode, {
-
            suppressErrors: true,
-
          });
-

-
          if (isValid) {
-
            // Generate a unique id for this diagram
-
            const diagramId = `mermaid-${Math.random().toString(36).substr(2, 9)}`;
-

-
            // Try to render the mermaid diagram
-
            const { svg } = await mermaid.render(diagramId, mermaidCode);
-

-
            // Replace the code block with the rendered diagram
+
          const { svg } = await mermaid.render(
+
            diagramId,
+
            node.textContent ?? "",
+
          );
+
          // The component may have re-rendered or unmounted while we were
+
          // awaiting; in that case `preWrapper` is detached and there's
+
          // nothing to replace.
+
          if (preWrapper.isConnected) {
            const mermaidDiv = document.createElement("div");
            mermaidDiv.innerHTML = svg;
            mermaidDiv.classList.add("mermaid-diagram");
            preWrapper.replaceWith(mermaidDiv);
          }
        } catch (error) {
-
          // On any error, keep the raw text visible (do nothing, let it render as code)
+
          console.warn("Not able to render mermaid diagram", error);
+
          if (preWrapper.isConnected) {
+
            const warning = document.createElement("div");
+
            warning.classList.add("mermaid-error");
+
            const icon = document.createElement("radicle-icon-small");
+
            icon.setAttribute("name", "warning");
+
            warning.appendChild(icon);
+
            const text = document.createElement("span");
+
            text.textContent = "Couldn't render diagram";
+
            warning.appendChild(text);
+
            preWrapper.before(warning);
+
          }
        }
        continue;
      }
@@ -562,6 +570,15 @@
    max-width: 100%;
    height: auto;
  }
+

+
  .markdown :global(.mermaid-error) {
+
    display: flex;
+
    align-items: center;
+
    gap: 0.375rem;
+
    color: var(--color-feedback-warning-text);
+
    font: var(--txt-body-s-regular);
+
    margin: 1rem 0 0.25rem;
+
  }
</style>

{#if frontMatter && frontMatter.length > 0}
modified tests/fixtures/repos/markdown.tar.bz2
modified tests/visual/desktop/markdown.spec.ts
@@ -125,20 +125,26 @@ test("math", async ({ page }) => {
  await expect(page).toHaveScreenshot({ fullPage: true });
});

-
test("mermaid diagram", async ({ page }) => {
+
test("mermaid diagrams", async ({ page }) => {
  await page.goto(`${markdownUrl}/tree/main/mermaid.md`, {
    waitUntil: "networkidle",
  });
-
  await expect(page.locator(".mermaid-diagram svg")).toBeVisible();
+
  await expect(page.locator(".mermaid-diagram svg")).toHaveCount(2);
  await expect(page).toHaveScreenshot({ fullPage: true });
});

-
test("broken mermaid falls back to code block", async ({ page }) => {
+
test("broken mermaid shows warning and does not block subsequent diagrams", async ({
+
  page,
+
}) => {
  await page.goto(`${markdownUrl}/tree/main/mermaid-broken.md`, {
    waitUntil: "networkidle",
  });
+
  // Our custom warning replaces mermaid's built-in error overlay.
+
  await expect(page.locator(".mermaid-error")).toBeVisible();
+
  // The original source for the broken diagram stays visible.
  await expect(page.locator("pre code.language-mermaid")).toBeVisible();
-
  await expect(page.locator(".mermaid-diagram")).toHaveCount(0);
+
  // The valid diagram following the broken one still renders.
+
  await expect(page.locator(".mermaid-diagram svg")).toHaveCount(1);
  await expect(page).toHaveScreenshot({ fullPage: true });
});