From 1cbf1577539b43208ea50c6f2d886174ab6c08ea Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Thu, 1 Oct 2026 15:22:53 +0200 Subject: [PATCH 1/4] feat(react): render ```dot code blocks as Graphviz diagrams When the optional `@hpcc-js/wasm-graphviz` dependency is installed, ```dot code blocks become inline SVG diagrams. Colors written as `${light|dark}` render a diagram per color scheme. Assisted-by: Claude Opus 5.5 --- packages/react/package.json | 9 +++ .../src/jsx-ast/utils/plugins/diagrams.mjs | 76 +++++++++++++++++++ packages/react/src/jsx-ast/utils/remark.mjs | 2 + pnpm-lock.yaml | 8 ++ 4 files changed, 95 insertions(+) create mode 100644 packages/react/src/jsx-ast/utils/plugins/diagrams.mjs diff --git a/packages/react/package.json b/packages/react/package.json index 843b1f89..48e2b01e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -59,6 +59,15 @@ "vite": "~8.3.1" }, "devDependencies": { + "@hpcc-js/wasm-graphviz": "^1.22.0", "dedent": "^1.7.2" + }, + "peerDependencies": { + "@hpcc-js/wasm-graphviz": "^1.22.0" + }, + "peerDependenciesMeta": { + "@hpcc-js/wasm-graphviz": { + "optional": true + } } } diff --git a/packages/react/src/jsx-ast/utils/plugins/diagrams.mjs b/packages/react/src/jsx-ast/utils/plugins/diagrams.mjs new file mode 100644 index 00000000..8a39e0f5 --- /dev/null +++ b/packages/react/src/jsx-ast/utils/plugins/diagrams.mjs @@ -0,0 +1,76 @@ +'use strict'; + +import { visit } from 'unist-util-visit'; + +/** + * Graphviz, when the optional `@hpcc-js/wasm-graphviz` dependency is + * installed. Loaded up front, as the Markdown pipeline runs synchronously. + * + * @type {import('@hpcc-js/wasm-graphviz').Graphviz | undefined} + */ +const graphviz = await import('@hpcc-js/wasm-graphviz') + .then(({ Graphviz }) => Graphviz.load()) + .catch(() => undefined); + +/** A color for each color scheme: `${#3c3c43|#dfdfd6}` */ +const THEMED_COLOR = /\$\{([^|}]*)\|([^}]*)\}/g; + +/** + * Renders DOT source as an inline SVG. + * + * @param {string} dot + */ +const render = dot => + graphviz + .dot(dot, 'svg_inline') + // A blank line would end the HTML block the SVG is embedded in + .replace(/\n\s*\n/g, '\n') + .trim(); + +/** + * Renders a diagram. One using colors for each color scheme renders twice, + * one SVG per scheme, the page's styles showing the one of the current + * scheme. + * + * @param {string} dot + */ +export const renderDiagram = dot => { + if (!dot.includes('${')) { + return `
${render(dot)}
`; + } + + return ['light', 'dark'] + .map(scheme => { + const colors = dot.replace(THEMED_COLOR, (_, light, dark) => + scheme === 'light' ? light : dark + ); + + return `
${render(colors)}
`; + }) + .join('\n'); +}; + +/** + * Renders ```dot code blocks as Graphviz diagrams, when Graphviz is + * installed; otherwise, they stay code blocks. + * + * @param {import('mdast').Root} tree + */ +const transformer = tree => { + if (!graphviz) { + return; + } + + visit(tree, 'code', (node, index, parent) => { + if (node.lang !== 'dot') { + return; + } + + parent.children[index] = { type: 'html', value: renderDiagram(node.value) }; + }); +}; + +/** + * The remark plugin rendering Graphviz diagrams. + */ +export default () => transformer; diff --git a/packages/react/src/jsx-ast/utils/remark.mjs b/packages/react/src/jsx-ast/utils/remark.mjs index 59bafd01..148e6946 100644 --- a/packages/react/src/jsx-ast/utils/remark.mjs +++ b/packages/react/src/jsx-ast/utils/remark.mjs @@ -15,6 +15,7 @@ import { visit } from 'unist-util-visit'; import { AST_NODE_TYPES } from '../constants.mjs'; import transformAlerts from './plugins/alerts.mjs'; +import transformDiagrams from './plugins/diagrams.mjs'; import transformElements from './plugins/transformer.mjs'; const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)]; @@ -58,6 +59,7 @@ export const getRemarkRecma = lazy(() => unified() .use(remarkParse) .use(transformAlerts) + .use(transformDiagrams) // We make Rehype ignore existing HTML nodes, and JSX nodes // as these are nodes we manually created during the generation process // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 527830e0..9fdc698f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,6 +302,9 @@ importers: specifier: ~8.3.1 version: 8.3.1(@types/node@24.10.15)(yaml@2.9.1) devDependencies: + '@hpcc-js/wasm-graphviz': + specifier: ^1.22.0 + version: 1.29.2 dedent: specifier: ^1.7.2 version: 1.7.2 @@ -538,6 +541,9 @@ packages: peerDependencies: react: '>= 16 || ^19.0.0-rc' + '@hpcc-js/wasm-graphviz@1.29.2': + resolution: {integrity: sha512-AkArBAZERT5OxH52/+kvCHB07cZ6ZNd9GnOvjw+S880bR88D6i27XfK6xj4dBhUDxNA2QPPMdgEcky3x4TJ3KA==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -3404,6 +3410,8 @@ snapshots: dependencies: react: 19.2.8 + '@hpcc-js/wasm-graphviz@1.29.2': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 From 16896bc277a83597c94d2248b9cad401affb168c Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Thu, 1 Oct 2026 15:22:55 +0200 Subject: [PATCH 2/4] feat(react): show the diagram of the current color scheme Assisted-by: Claude Opus 5.5 --- packages/react/src/html/ui/index.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/react/src/html/ui/index.css b/packages/react/src/html/ui/index.css index 6653b39e..09f595e6 100644 --- a/packages/react/src/html/ui/index.css +++ b/packages/react/src/html/ui/index.css @@ -204,4 +204,18 @@ main { } } } + + /* Graphviz diagrams; with colors for each color scheme, there is one per + scheme, and the one of the current scheme shows */ + .diagram svg { + display: block; + max-width: 100%; + height: auto; + margin: 1rem auto; + } + + :where([data-theme='dark']) & .diagram-light, + :where(:root:not([data-theme='dark'])) & .diagram-dark { + display: none; + } } From 97df4bcaeef5be720c427304ddca5b2c9e01756c Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Thu, 1 Oct 2026 15:22:56 +0200 Subject: [PATCH 3/4] test(react): cover the Graphviz diagrams plugin Assisted-by: Claude Opus 5.5 --- .../utils/plugins/__tests__/diagrams.test.mjs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packages/react/src/jsx-ast/utils/plugins/__tests__/diagrams.test.mjs diff --git a/packages/react/src/jsx-ast/utils/plugins/__tests__/diagrams.test.mjs b/packages/react/src/jsx-ast/utils/plugins/__tests__/diagrams.test.mjs new file mode 100644 index 00000000..4b7754d7 --- /dev/null +++ b/packages/react/src/jsx-ast/utils/plugins/__tests__/diagrams.test.mjs @@ -0,0 +1,44 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { u } from 'unist-builder'; + +import transformDiagrams, { renderDiagram } from '../diagrams.mjs'; + +const transform = transformDiagrams(); + +describe('transformDiagrams', () => { + it('renders ```dot code blocks as inline SVG', () => { + const tree = u('root', [u('code', { lang: 'dot' }, 'digraph { a -> b }')]); + + transform(tree); + + const [node] = tree.children; + assert.equal(node.type, 'html'); + assert.match( + node.value, + /^
[\s\S]*<\/div>$/ + ); + assert.doesNotMatch(node.value, /\n\s*\n/); + }); + + it('leaves other code blocks alone', () => { + const code = u('code', { lang: 'js' }, 'a -> b'); + const tree = u('root', [code]); + + transform(tree); + + assert.equal(tree.children[0], code); + }); +}); + +describe('renderDiagram', () => { + it('renders a diagram per color scheme for themed colors', () => { + const html = renderDiagram('digraph { a [color="${#000000|#ffffff}"] }'); + + assert.match(html, /class="diagram diagram-light"/); + assert.match(html, /class="diagram diagram-dark"/); + assert.match(html.split('diagram-dark')[0], /#000000/); + assert.match(html.split('diagram-dark')[1], /#ffffff/); + }); +}); From 665a670024aa8a2696ec119077f64a55bffb81a4 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Thu, 1 Oct 2026 15:22:58 +0200 Subject: [PATCH 4/4] chore: add a changeset for Graphviz diagrams Assisted-by: Claude Opus 5.5 --- .changeset/graphviz-diagrams.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/graphviz-diagrams.md diff --git a/.changeset/graphviz-diagrams.md b/.changeset/graphviz-diagrams.md new file mode 100644 index 00000000..6fa8d856 --- /dev/null +++ b/.changeset/graphviz-diagrams.md @@ -0,0 +1,5 @@ +--- +'@doc-kit/generator-react': minor +--- + +Render ` ```dot ` code blocks as Graphviz diagrams when the optional `@hpcc-js/wasm-graphviz` dependency is installed, with `${light|dark}` colors rendering a diagram per color scheme.