From 9cdaf1f6c1becbfb8bea5b7fdc0d6512b1f40d80 Mon Sep 17 00:00:00 2001 From: wallpants <47203170+wallpants@users.noreply.github.com> Date: Mon, 3 Aug 2026 21:26:41 -0600 Subject: [PATCH 1/2] build(deps): bump pantsdown to 2.3.0 Brings interactive pan/zoom controls and fullscreen popover for mermaid diagrams. --- bun.lock | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index d2c0791..55c3d0a 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ "isbinaryfile": "6.0.0", "mermaid": "^11.16.0", "opener": "^1.5.2", - "pantsdown": "2.2.8", + "pantsdown": "2.3.0", "react": "^19.2.8", "react-dom": "^19.2.8", "reconnecting-websocket": "^4.4.0", @@ -988,7 +988,7 @@ "package-manager-detector": ["package-manager-detector@1.7.0", "", {}, "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ=="], - "pantsdown": ["pantsdown@2.2.8", "", { "dependencies": { "github-slugger": "^2.0.0", "highlight.js": "^11.11.1", "katex": "^0.18.0" } }, "sha512-fARVV5orolh9FP8cP0vsCUqgWp56B4Tcj7qhXH8dNLkA3CoeVu5/tK02dvWk7mDW0eVB9rSlrVMmRdZlYPwSIw=="], + "pantsdown": ["pantsdown@2.3.0", "", { "dependencies": { "github-slugger": "^2.0.0", "highlight.js": "^11.11.1", "katex": "^0.18.1" } }, "sha512-pkAfc4FraYphkalL0HYmLxJUZ4M/mC3hW82vm+7gbfEqsuYbPrx09RvCxFcCyryPpO9eyV57QSvFZ6RK66eioA=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], @@ -1616,7 +1616,7 @@ "ora/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - "pantsdown/katex": ["katex@0.18.0", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-rZ4Sw94Oja12Ib4+ee7inAr//yxj0G8RaAlh+ZpzfFhwTifGeJbqJ7D0FStcv6EV8DeNNU9Y8rPeg6vPUizlqg=="], + "pantsdown/katex": ["katex@0.18.1", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Td8GCYSxDAoMhHOlKmCFMJ/hz5qlAAb71n66Dryw9nfCVfumLo7nhuotbvKom/XPADmrYC3O5QR71EPq4DarJQ=="], "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], diff --git a/package.json b/package.json index 53f4e2d..182c9cd 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "isbinaryfile": "6.0.0", "mermaid": "^11.16.0", "opener": "^1.5.2", - "pantsdown": "2.2.8", + "pantsdown": "2.3.0", "react": "^19.2.8", "react-dom": "^19.2.8", "reconnecting-websocket": "^4.4.0", From 8110f037fbde53cdff1d6ca38c07ec8a5faf482f Mon Sep 17 00:00:00 2001 From: wallpants <47203170+wallpants@users.noreply.github.com> Date: Mon, 3 Aug 2026 21:26:51 -0600 Subject: [PATCH 2/2] fix(mermaid): keep memo clean of pantsdown controls, cache failed renders Carry the known-clean svg forward in the data-rendered branch instead of re-reading innerHTML: pantsdown 2.3.0 wraps rendered diagrams with pan/zoom controls after render, and serializing those into the memo would re-inject duplicated, listener-less controls on every update. Also memoize null for definitions that fail mermaid.render, so a broken diagram (common mid-edit) is not re-attempted on every keystroke elsewhere in the file. Failures are deterministic per definition; fixing the source changes the key and renders normally. --- app/web/components/markdown/mermaid.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/web/components/markdown/mermaid.ts b/app/web/components/markdown/mermaid.ts index db05219..fa865ac 100644 --- a/app/web/components/markdown/mermaid.ts +++ b/app/web/components/markdown/mermaid.ts @@ -1,7 +1,10 @@ import mermaid, { type MermaidConfig } from "mermaid"; export const myMermaid = { - memoMermaids: new Map(), + // null marks a definition that failed to render: mermaid parse errors are + // deterministic, so the same source is never re-attempted (fixing the + // diagram changes the source and therefore the key) + memoMermaids: new Map(), incId: 0, initialize(config: MermaidConfig) { @@ -15,13 +18,20 @@ export const myMermaid = { renderMemoized(element?: HTMLElement) { const documentMermaids = (element ?? document).querySelectorAll(".mermaid"); - const memoMermaids = new Map(); + const memoMermaids = new Map(); const pendingMermaids: Element[] = []; for (const dMermaid of documentMermaids) { const renderedDefinition = dMermaid.getAttribute("data-rendered"); if (renderedDefinition) { - memoMermaids.set(renderedDefinition, dMermaid.innerHTML); + // carry the clean svg forward instead of re-reading innerHTML: + // pantsdown's script wraps the svg with pan/zoom controls after + // render, and serializing those back into the memo would + // duplicate them (dead, listener-less) on every re-render + memoMermaids.set( + renderedDefinition, + this.memoMermaids.get(renderedDefinition) ?? dMermaid.innerHTML, + ); continue; } @@ -33,6 +43,9 @@ export const myMermaid = { memoMermaids.set(definition, svg); dMermaid.setAttribute("data-rendered", definition); dMermaid.innerHTML = svg; + } else if (svg === null) { + // known-broken definition: keep showing the source, don't retry + memoMermaids.set(definition, null); } else { pendingMermaids.push(dMermaid); } @@ -59,18 +72,18 @@ export const myMermaid = { let svg = this.memoMermaids.get(definition); - if (!svg) { + if (svg === undefined) { try { const { svg: newSvg } = await mermaid.render(`mermaid-${++this.incId}`, definition); svg = newSvg; } catch (_) { - // + svg = null; } } + memoMermaids.set(definition, svg); if (!svg) continue; - memoMermaids.set(definition, svg); dMermaid.innerHTML = svg; }