From a5c7f55ce36b8cce3c9b68a62ca9b385020e32ca Mon Sep 17 00:00:00 2001 From: Eric Windmill Date: Tue, 18 Aug 2026 13:59:10 -0700 Subject: [PATCH 1/4] Add client-side Mermaid diagram rendering support --- .../common/client/mermaid_diagram.dart | 156 ++++++++++++++++++ packages/site_shared/lib/page_extensions.dart | 1 + .../lib/src/extensions/mermaid_processor.dart | 34 ++++ .../lib/src/markdown/markdown_parser.dart | 2 + .../lib/src/markdown/mermaid_syntax.dart | 44 +++++ sites/docs/lib/main.client.options.dart | 6 + sites/docs/lib/main.server.options.dart | 10 ++ sites/docs/lib/src/extensions/registry.dart | 1 + 8 files changed, 254 insertions(+) create mode 100644 packages/site_shared/lib/components/common/client/mermaid_diagram.dart create mode 100644 packages/site_shared/lib/src/extensions/mermaid_processor.dart create mode 100644 packages/site_shared/lib/src/markdown/mermaid_syntax.dart diff --git a/packages/site_shared/lib/components/common/client/mermaid_diagram.dart b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart new file mode 100644 index 00000000000..de0cf0a9284 --- /dev/null +++ b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart @@ -0,0 +1,156 @@ +import 'dart:async'; + +import 'package:jaspr/dom.dart'; +import 'package:jaspr/jaspr.dart'; +import 'package:universal_web/js_interop.dart'; +import 'package:universal_web/web.dart' as web; + +@client +final class MermaidViewer extends StatefulComponent { + const MermaidViewer({required this.diagram, super.key}); + + final String diagram; + + @override + State createState() => _MermaidViewerState(); +} + +final class _MermaidViewerState extends State { + static int _idCounter = 0; + final String _diagramId = 'mermaid-diagram-${_idCounter++}'; + + String? _svg; + web.MutationObserver? _themeObserver; + + @override + void initState() { + super.initState(); + if (kIsWeb) { + context.binding.addPostFrameCallback(() { + unawaited(_renderDiagram()); + _observeTheme(); + }); + } + } + + @override + void dispose() { + _themeObserver?.disconnect(); + super.dispose(); + } + + void _observeTheme() { + final body = web.document.body; + if (body == null) return; + + var isDark = body.classList.contains('dark-mode'); + _themeObserver = web.MutationObserver( + ((JSArray _, web.MutationObserver _) { + final newIsDark = body.classList.contains('dark-mode'); + if (newIsDark != isDark) { + isDark = newIsDark; + unawaited(_renderDiagram()); + } + }).toJS, + ); + + _themeObserver?.observe( + body, + web.MutationObserverInit( + attributes: true, + attributeFilter: ['class'.toJS].toJS, + ), + ); + } + + Future _renderDiagram() async { + try { + final isDark = + web.document.body?.classList.contains('dark-mode') ?? false; + final theme = isDark ? 'dark' : 'default'; + + // Dynamically import Mermaid ESM from CDN only when a + // diagram component mounts + final module = await _importMermaid(); + final mermaid = module.defaultExport; + + mermaid.initialize( + MermaidConfig( + startOnLoad: false.toJS, + theme: theme.toJS, + securityLevel: 'loose'.toJS, + ), + ); + + final result = await mermaid + .render(_diagramId.toJS, component.diagram.toJS) + .toDart; + + if (mounted) { + setState(() { + _svg = result.svg.toDart; + }); + } + } catch (e) { + if (kDebugMode) { + print('Failed to render Mermaid diagram: $e'); + } + } + } + + @override + Component build(BuildContext context) { + return div( + classes: 'mermaid-container', + [ + if (_svg case final svg?) + RawText(svg) + else + // Fallback during SSR or while loading + pre( + classes: 'mermaid', + attributes: {'data-source': component.diagram}, + [.text(component.diagram)], + ), + ], + ); + } +} + +// ----------------------------------------------------------------------------- +// JS Interop Bindings (Dart 3 Extension Types) +// ----------------------------------------------------------------------------- + +@JS('eval') +external JSAny? _eval(JSString code); + +Future _importMermaid() { + final importFn = _eval('(url) => import(url)'.toJS) as JSFunction; + final promise = importFn.callAsFunction( + null, + 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'.toJS, + ) as JSPromise; + return promise.toDart; +} + +extension type MermaidModule._(JSObject _) implements JSObject { + @JS('default') + external MermaidApi get defaultExport; +} + +extension type MermaidApi._(JSObject _) implements JSObject { + external void initialize(MermaidConfig config); + external JSPromise render(JSString id, JSString text); +} + +extension type MermaidConfig._(JSObject _) implements JSObject { + external factory MermaidConfig({ + JSBoolean? startOnLoad, + JSString? theme, + JSString? securityLevel, + }); +} + +extension type MermaidRenderResult._(JSObject _) implements JSObject { + external JSString get svg; +} diff --git a/packages/site_shared/lib/page_extensions.dart b/packages/site_shared/lib/page_extensions.dart index 3b889b65c0b..e99249c0e16 100644 --- a/packages/site_shared/lib/page_extensions.dart +++ b/packages/site_shared/lib/page_extensions.dart @@ -9,4 +9,5 @@ export 'src/extensions/attribute_processor.dart'; export 'src/extensions/code_block_processor.dart'; export 'src/extensions/header_extractor.dart'; export 'src/extensions/header_processor.dart'; +export 'src/extensions/mermaid_processor.dart'; export 'src/extensions/table_processor.dart'; diff --git a/packages/site_shared/lib/src/extensions/mermaid_processor.dart b/packages/site_shared/lib/src/extensions/mermaid_processor.dart new file mode 100644 index 00000000000..b5c721037e0 --- /dev/null +++ b/packages/site_shared/lib/src/extensions/mermaid_processor.dart @@ -0,0 +1,34 @@ +import 'package:jaspr_content/jaspr_content.dart'; + +import '../../components/common/client/mermaid_diagram.dart'; + +final class MermaidProcessor implements PageExtension { + const MermaidProcessor(); + + @override + Future> apply(Page page, List nodes) async => + _processNodes(nodes); + + List _processNodes(List nodes) { + return [ + for (final node in nodes) + if (node case ElementNode( + tag: 'div', + attributes: {'class': 'mermaid-container'}, + children: [ + ElementNode(attributes: {'data-source': final diagram}), + ..., + ], + )) + ComponentNode(MermaidViewer(diagram: diagram)) + else if (node is ElementNode) + ElementNode( + node.tag, + node.attributes, + node.children != null ? _processNodes(node.children!) : null, + ) + else + node, + ]; + } +} diff --git a/packages/site_shared/lib/src/markdown/markdown_parser.dart b/packages/site_shared/lib/src/markdown/markdown_parser.dart index 60165d98478..dc15c277bc4 100644 --- a/packages/site_shared/lib/src/markdown/markdown_parser.dart +++ b/packages/site_shared/lib/src/markdown/markdown_parser.dart @@ -18,10 +18,12 @@ import 'alert_syntax.dart'; import 'attribute_syntax.dart'; import 'fenced_code_block_syntax.dart'; import 'header_syntax.dart'; +import 'mermaid_syntax.dart'; /// The `package:markdown` block syntaxes to apply when parsing Markdown. const List _blockSyntaxes = [ JasprHtmlBlockSyntax(), + MermaidBlockSyntax(), CustomFencedCodeBlockSyntax(), HeaderWithAttributesSyntax(), AttributeBlockSyntax(), diff --git a/packages/site_shared/lib/src/markdown/mermaid_syntax.dart b/packages/site_shared/lib/src/markdown/mermaid_syntax.dart new file mode 100644 index 00000000000..b4c62535a5b --- /dev/null +++ b/packages/site_shared/lib/src/markdown/mermaid_syntax.dart @@ -0,0 +1,44 @@ +import 'package:markdown/markdown.dart' as md; + +final class MermaidBlockSyntax extends md.BlockSyntax { + const MermaidBlockSyntax(); + + // Matches opening fence: ```mermaid (with optional trailing whitespace/config) + @override + RegExp get pattern => RegExp(r'^\s{0,3}`{3,}mermaid(?:\s.*)?$'); + + static final _closingFencePattern = RegExp(r'^\s{0,3}`{3,}\s*$'); + + @override + bool canParse(md.BlockParser parser) { + return pattern.hasMatch(parser.current.content); + } + + @override + md.Node? parse(md.BlockParser parser) { + // Advance past the opening ```mermaid line + parser.advance(); + + final lines = []; + + // Collect diagram definition until the closing ``` + while (!parser.isDone) { + final line = parser.current.content; + if (_closingFencePattern.hasMatch(line)) { + parser.advance(); // Consume closing fence + break; + } + lines.add(line); + parser.advance(); + } + + final rawContent = lines.join('\n'); + + // Return HTML AST node for the diagram container + final pre = md.Element.text('pre', rawContent) + ..attributes['class'] = 'mermaid' + ..attributes['data-source'] = rawContent; + + return md.Element('div', [pre])..attributes['class'] = 'mermaid-container'; + } +} diff --git a/sites/docs/lib/main.client.options.dart b/sites/docs/lib/main.client.options.dart index 23c1b571e81..ac296cdcf47 100644 --- a/sites/docs/lib/main.client.options.dart +++ b/sites/docs/lib/main.client.options.dart @@ -30,6 +30,8 @@ import 'package:site_shared/components/common/client/download_button.dart' deferred as _download_button; import 'package:site_shared/components/common/client/feedback.dart' deferred as _feedback; +import 'package:site_shared/components/common/client/mermaid_diagram.dart' + deferred as _mermaid_diagram; import 'package:site_shared/components/common/client/on_this_page_button.dart' deferred as _on_this_page_button; import 'package:site_shared/components/common/client/page_header_options.dart' @@ -140,6 +142,10 @@ ClientOptions get defaultClientOptions => ClientOptions( (p) => _feedback.FeedbackComponent(issueUrl: p['issueUrl'] as String), loader: _feedback.loadLibrary, ), + 'site_shared:mermaid_diagram': ClientLoader( + (p) => _mermaid_diagram.MermaidViewer(diagram: p['diagram'] as String), + loader: _mermaid_diagram.loadLibrary, + ), 'site_shared:on_this_page_button': ClientLoader( (p) => _on_this_page_button.OnThisPageButton(), loader: _on_this_page_button.loadLibrary, diff --git a/sites/docs/lib/main.server.options.dart b/sites/docs/lib/main.server.options.dart index fcbd5e0a090..03bb51110df 100644 --- a/sites/docs/lib/main.server.options.dart +++ b/sites/docs/lib/main.server.options.dart @@ -30,6 +30,8 @@ import 'package:site_shared/components/common/client/download_button.dart' as _download_button; import 'package:site_shared/components/common/client/feedback.dart' as _feedback; +import 'package:site_shared/components/common/client/mermaid_diagram.dart' + as _mermaid_diagram; import 'package:site_shared/components/common/client/on_this_page_button.dart' as _on_this_page_button; import 'package:site_shared/components/common/client/page_header_options.dart' @@ -114,6 +116,11 @@ ServerOptions get defaultServerOptions => ServerOptions( 'site_shared:feedback', params: __feedbackFeedbackComponent, ), + _mermaid_diagram.MermaidViewer: + ClientTarget<_mermaid_diagram.MermaidViewer>( + 'site_shared:mermaid_diagram', + params: __mermaid_diagramMermaidViewer, + ), _on_this_page_button.OnThisPageButton: ClientTarget<_on_this_page_button.OnThisPageButton>( 'site_shared:on_this_page_button', @@ -180,6 +187,9 @@ Map __download_buttonDownloadButton( Map __feedbackFeedbackComponent( _feedback.FeedbackComponent c, ) => {'issueUrl': c.issueUrl}; +Map __mermaid_diagramMermaidViewer( + _mermaid_diagram.MermaidViewer c, +) => {'diagram': c.diagram}; Map __page_header_optionsPageHeaderOptions( _page_header_options.PageHeaderOptions c, ) => { diff --git a/sites/docs/lib/src/extensions/registry.dart b/sites/docs/lib/src/extensions/registry.dart index 158f0a3a9c9..51993e1576c 100644 --- a/sites/docs/lib/src/extensions/registry.dart +++ b/sites/docs/lib/src/extensions/registry.dart @@ -16,6 +16,7 @@ const List allNodeProcessingExtensions = [ HeaderExtractorExtension(), HeaderWrapperExtension(), TableWrapperExtension(), + MermaidProcessor(), CodeBlockProcessor(defaultTitle: 'Runnable Flutter example'), GlossaryLinkProcessor(), TutorialNavigationExtension(), From 54f30b0cb37f435d5537e6d5410e4171c523093f Mon Sep 17 00:00:00 2001 From: Eric Windmill Date: Tue, 18 Aug 2026 14:41:15 -0700 Subject: [PATCH 2/4] Add initial mermaid scss files --- .../lib/_sass/components/_mermaid.scss | 55 +++++++++++++++++ .../common/client/mermaid_diagram.dart | 2 + .../lib/src/markdown/mermaid_syntax.dart | 15 +++++ sites/docs/lib/_sass/_site.scss | 1 + sites/docs/src/content/ai/evals.md | 17 ++++++ .../src/content/contribute/docs/markdown.md | 59 +++++++++++++++++++ sites/www/lib/styles/styles.scss | 1 + 7 files changed, 150 insertions(+) create mode 100644 packages/site_shared/lib/_sass/components/_mermaid.scss diff --git a/packages/site_shared/lib/_sass/components/_mermaid.scss b/packages/site_shared/lib/_sass/components/_mermaid.scss new file mode 100644 index 00000000000..eebefd8372a --- /dev/null +++ b/packages/site_shared/lib/_sass/components/_mermaid.scss @@ -0,0 +1,55 @@ +// Copyright 2026 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +.mermaid-container { + display: flex; + justify-content: center; + margin: 1.75rem 0; + padding: 1.5rem 1rem; + overflow-x: auto; + background-color: var(--site-raised-bgColor-translucent); + border: 1px solid var(--site-outline-variant); + border-radius: var(--site-radius); + + // Fallback while loading or during SSR + pre.mermaid { + margin: 0; + padding: 0; + background: transparent; + border: none; + font-family: var(--site-code-fontFamily); + } + + // Rendered SVG styling + // You must use !important to override styles for specific elements within + // the rendered SVG. For styling individual graphs, use Mermaid's built-in + // classRef system + svg { + max-width: 100%; + height: auto; + + // Use Google Sans Flex for diagram labels + text, + .label, + .nodeLabel, + .edgeLabel { + font-family: var(--site-ui-fontFamily), sans-serif !important; + } + + // Sharper node outlines matching site borders + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + stroke-width: 1.5px; + fill: var(--site-secondaryContainer-bgColor) !important; + } + + // Edge lines & arrows + .edgePath .path { + stroke-width: 1.5px; + } + } +} diff --git a/packages/site_shared/lib/components/common/client/mermaid_diagram.dart b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart index de0cf0a9284..da4c207f1ba 100644 --- a/packages/site_shared/lib/components/common/client/mermaid_diagram.dart +++ b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart @@ -79,6 +79,7 @@ final class _MermaidViewerState extends State { startOnLoad: false.toJS, theme: theme.toJS, securityLevel: 'loose'.toJS, + fontFamily: 'Google Sans Flex, sans-serif'.toJS, ), ); @@ -148,6 +149,7 @@ extension type MermaidConfig._(JSObject _) implements JSObject { JSBoolean? startOnLoad, JSString? theme, JSString? securityLevel, + JSString? fontFamily, }); } diff --git a/packages/site_shared/lib/src/markdown/mermaid_syntax.dart b/packages/site_shared/lib/src/markdown/mermaid_syntax.dart index b4c62535a5b..e476e33b059 100644 --- a/packages/site_shared/lib/src/markdown/mermaid_syntax.dart +++ b/packages/site_shared/lib/src/markdown/mermaid_syntax.dart @@ -1,5 +1,20 @@ import 'package:markdown/markdown.dart' as md; +/// A custom Markdown block syntax for diagrams authored +/// between ```mermaid code fences. +/// +/// Example: +/// +/// ````markdown +/// ```mermaid +/// flowchart TD +/// A --> B +/// ``` +/// ```` +/// +/// This renders as a `
` containing +/// a `
` element that hydrates on the client
+/// via [MermaidViewer].
 final class MermaidBlockSyntax extends md.BlockSyntax {
   const MermaidBlockSyntax();
 
diff --git a/sites/docs/lib/_sass/_site.scss b/sites/docs/lib/_sass/_site.scss
index 99092c6a676..8160f161328 100644
--- a/sites/docs/lib/_sass/_site.scss
+++ b/sites/docs/lib/_sass/_site.scss
@@ -38,6 +38,7 @@
 @use 'package:site_shared/_sass/components/cookie-notice';
 @use 'package:site_shared/_sass/components/dropdown';
 @use 'package:site_shared/_sass/components/menu-toggle';
+@use 'package:site_shared/_sass/components/mermaid';
 @use 'package:site_shared/_sass/components/progress-ring';
 @use 'package:site_shared/_sass/components/quiz';
 @use 'package:site_shared/_sass/components/site-switcher';
diff --git a/sites/docs/src/content/ai/evals.md b/sites/docs/src/content/ai/evals.md
index e4985cc52b3..bc6767f2778 100644
--- a/sites/docs/src/content/ai/evals.md
+++ b/sites/docs/src/content/ai/evals.md
@@ -28,3 +28,20 @@ Evals measure both deterministic code correctness
 (compilation, lints, automated tests) and qualitative performance
 (reasoning, safety, and conciseness) using automated model judges
 and expert human grading.
+
+```mermaid
+flowchart TB
+    c1:::myStyle-->a2
+    subgraph one
+    a1-->a2
+    end
+    subgraph two
+    b1-->b2
+    end
+    subgraph three
+    c1-->c2
+    end
+    one --> two
+    three --> two
+    two --> c2
+```
\ No newline at end of file
diff --git a/sites/docs/src/content/contribute/docs/markdown.md b/sites/docs/src/content/contribute/docs/markdown.md
index fa62002c26b..ca9ac2939b5 100644
--- a/sites/docs/src/content/contribute/docs/markdown.md
+++ b/sites/docs/src/content/contribute/docs/markdown.md
@@ -47,3 +47,62 @@ To learn more about customizing code blocks,
 check out the dedicated documentation on [Code blocks][].
 
 [Code blocks]: /contribute/docs/code-blocks
+
+
+## Mermaid diagrams
+
+To render flowcharts, sequence diagrams, and other charts within a Markdown file,
+use a fenced code block with the `mermaid` language identifier:
+
+```markdown
+```mermaid
+flowchart LR
+    A[Start] --> B(Process)
+    B --> C{Decision}
+    C -->|Yes| D[Done]
+    C -->|No| B
+```
+```
+
+### Styling Mermaid diagrams
+
+Mermaid diagrams automatically adapt to the site's light and dark themes
+and inherit the default `Google Sans Flex` typography.
+
+#### In-diagram styling (Recommended)
+
+When you need custom colors for specific nodes,
+define them directly in the diagram using Mermaid's built-in
+[`classDef` and `:::styleName` syntax][]:
+
+```mermaid
+flowchart LR
+    A:::highlightNode --> B
+    classDef highlightNode fill:#f96,stroke:#333,stroke-width:2px;
+```
+
+#### SCSS styling
+
+To adjust the global diagram layout, border, or background across the site,
+edit [`_mermaid.scss`][]:
+
+```scss
+.mermaid-container {
+  // Container styling (padding, margins, background, border)
+
+  // Fallback while loading or during SSR
+  pre.mermaid {
+    // Pre-loading styles
+  }
+
+  svg {
+    // Custom SVG element overrides (requires !important)
+    .highlightNode rect {
+      fill: var(--site-primary-color) !important;
+    }
+  }
+}
+```
+
+[`classDef` and `:::styleName` syntax]: https://mermaid.js.org/syntax/flowchart.html#styling-and-classes
+[`_mermaid.scss`]: https://github.com/flutter/website/blob/main/packages/site_shared/lib/_sass/components/_mermaid.scss
diff --git a/sites/www/lib/styles/styles.scss b/sites/www/lib/styles/styles.scss
index f68b8bceeb2..163c61871d3 100644
--- a/sites/www/lib/styles/styles.scss
+++ b/sites/www/lib/styles/styles.scss
@@ -51,3 +51,4 @@
 @use 'package:site_shared/_sass/components/blog';
 @use 'package:site_shared/_sass/components/code';
 @use 'package:site_shared/_sass/components/dropdown';
+@use 'package:site_shared/_sass/components/mermaid';

From 9942358c9cd0f46bb62735f713a7545af3edfca7 Mon Sep 17 00:00:00 2001
From: Eric Windmill 
Date: Wed, 19 Aug 2026 07:57:19 -0700
Subject: [PATCH 3/4] use Dart package

---
 .../common/client/mermaid_diagram.dart        | 105 +++++-------------
 packages/site_shared/pubspec.yaml             |   6 +
 sites/www/lib/main.client.options.dart        |   6 +
 sites/www/lib/main.server.options.dart        |  10 ++
 4 files changed, 51 insertions(+), 76 deletions(-)

diff --git a/packages/site_shared/lib/components/common/client/mermaid_diagram.dart b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart
index da4c207f1ba..1cfbd496578 100644
--- a/packages/site_shared/lib/components/common/client/mermaid_diagram.dart
+++ b/packages/site_shared/lib/components/common/client/mermaid_diagram.dart
@@ -1,7 +1,6 @@
-import 'dart:async';
-
 import 'package:jaspr/dom.dart';
 import 'package:jaspr/jaspr.dart';
+import 'package:mermaid_core/mermaid_core.dart';
 import 'package:universal_web/js_interop.dart';
 import 'package:universal_web/web.dart' as web;
 
@@ -16,20 +15,29 @@ final class MermaidViewer extends StatefulComponent {
 }
 
 final class _MermaidViewerState extends State {
-  static int _idCounter = 0;
-  final String _diagramId = 'mermaid-diagram-${_idCounter++}';
-
   String? _svg;
   web.MutationObserver? _themeObserver;
 
   @override
   void initState() {
     super.initState();
+    final isDark =
+        kIsWeb && (web.document.body?.classList.contains('dark-mode') ?? false);
+    _svg = _renderDiagram(isDark: isDark);
+
     if (kIsWeb) {
-      context.binding.addPostFrameCallback(() {
-        unawaited(_renderDiagram());
-        _observeTheme();
-      });
+      _observeTheme();
+    }
+  }
+
+  @override
+  void didUpdateComponent(MermaidViewer oldComponent) {
+    super.didUpdateComponent(oldComponent);
+    if (oldComponent.diagram != component.diagram) {
+      final isDark =
+          kIsWeb &&
+          (web.document.body?.classList.contains('dark-mode') ?? false);
+      _svg = _renderDiagram(isDark: isDark);
     }
   }
 
@@ -49,7 +57,9 @@ final class _MermaidViewerState extends State {
         final newIsDark = body.classList.contains('dark-mode');
         if (newIsDark != isDark) {
           isDark = newIsDark;
-          unawaited(_renderDiagram());
+          setState(() {
+            _svg = _renderDiagram(isDark: isDark);
+          });
         }
       }).toJS,
     );
@@ -63,39 +73,21 @@ final class _MermaidViewerState extends State {
     );
   }
 
-  Future _renderDiagram() async {
+  String? _renderDiagram({required bool isDark}) {
     try {
-      final isDark =
-          web.document.body?.classList.contains('dark-mode') ?? false;
-      final theme = isDark ? 'dark' : 'default';
-
-      // Dynamically import Mermaid ESM from CDN only when a
-      // diagram component mounts
-      final module = await _importMermaid();
-      final mermaid = module.defaultExport;
-
-      mermaid.initialize(
-        MermaidConfig(
-          startOnLoad: false.toJS,
-          theme: theme.toJS,
-          securityLevel: 'loose'.toJS,
-          fontFamily: 'Google Sans Flex, sans-serif'.toJS,
-        ),
+      final theme =
+          isDark ? MermaidTheme.darkTheme : MermaidTheme.defaultTheme;
+      final mermaid = Mermaid(
+        measurer: const ApproximateTextMeasurer(),
+        theme: theme,
       );
-
-      final result = await mermaid
-          .render(_diagramId.toJS, component.diagram.toJS)
-          .toDart;
-
-      if (mounted) {
-        setState(() {
-          _svg = result.svg.toDart;
-        });
-      }
+      final scene = mermaid.render(component.diagram);
+      return renderSceneToSvg(scene);
     } catch (e) {
       if (kDebugMode) {
         print('Failed to render Mermaid diagram: $e');
       }
+      return null;
     }
   }
 
@@ -117,42 +109,3 @@ final class _MermaidViewerState extends State {
     );
   }
 }
-
-// -----------------------------------------------------------------------------
-// JS Interop Bindings (Dart 3 Extension Types)
-// -----------------------------------------------------------------------------
-
-@JS('eval')
-external JSAny? _eval(JSString code);
-
-Future _importMermaid() {
-  final importFn = _eval('(url) => import(url)'.toJS) as JSFunction;
-  final promise = importFn.callAsFunction(
-    null,
-    'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'.toJS,
-  ) as JSPromise;
-  return promise.toDart;
-}
-
-extension type MermaidModule._(JSObject _) implements JSObject {
-  @JS('default')
-  external MermaidApi get defaultExport;
-}
-
-extension type MermaidApi._(JSObject _) implements JSObject {
-  external void initialize(MermaidConfig config);
-  external JSPromise render(JSString id, JSString text);
-}
-
-extension type MermaidConfig._(JSObject _) implements JSObject {
-  external factory MermaidConfig({
-    JSBoolean? startOnLoad,
-    JSString? theme,
-    JSString? securityLevel,
-    JSString? fontFamily,
-  });
-}
-
-extension type MermaidRenderResult._(JSObject _) implements JSObject {
-  external JSString get svg;
-}
diff --git a/packages/site_shared/pubspec.yaml b/packages/site_shared/pubspec.yaml
index 4251c74efae..d0a85ca7fcd 100644
--- a/packages/site_shared/pubspec.yaml
+++ b/packages/site_shared/pubspec.yaml
@@ -16,6 +16,11 @@ dependencies:
   jaspr_content: ^0.5.3
   markdown: ^7.3.1
   markdown_description_list: ^0.2.0
+  mermaid_core:
+    git:
+      url: https://github.com/orestesgaolin/mermaid.git
+      ref: round3-fixes-and-packaging
+      path: packages/mermaid_core
   meta: ^1.18.2
   nanoid2: ^2.0.1
   opal: ^0.2.4
@@ -26,3 +31,4 @@ dependencies:
 
 dev_dependencies:
   analysis_defaults: any
+  test: ^1.31.1
diff --git a/sites/www/lib/main.client.options.dart b/sites/www/lib/main.client.options.dart
index 27a2638f576..0372da80744 100644
--- a/sites/www/lib/main.client.options.dart
+++ b/sites/www/lib/main.client.options.dart
@@ -34,6 +34,8 @@ import 'package:site_shared/components/common/client/collapse_button.dart'
     deferred as _collapse_button;
 import 'package:site_shared/components/common/client/copy_button.dart'
     deferred as _copy_button;
+import 'package:site_shared/components/common/client/mermaid_diagram.dart'
+    deferred as _mermaid_diagram;
 import 'package:site_shared/components/dartpad/dartpad_injector.dart'
     deferred as _dartpad_injector;
 import 'package:site_shared/components/utils/component_ref.dart'
@@ -181,6 +183,10 @@ ClientOptions get defaultClientOptions => ClientOptions(
       ),
       loader: _copy_button.loadLibrary,
     ),
+    'site_shared:mermaid_diagram': ClientLoader(
+      (p) => _mermaid_diagram.MermaidViewer(diagram: p['diagram'] as String),
+      loader: _mermaid_diagram.loadLibrary,
+    ),
     'site_shared:dartpad_injector': ClientLoader(
       (p) => _dartpad_injector.DartPadInjector(
         title: p['title'] as String,
diff --git a/sites/www/lib/main.server.options.dart b/sites/www/lib/main.server.options.dart
index 55b5d4f6e2d..84a754ecf76 100644
--- a/sites/www/lib/main.server.options.dart
+++ b/sites/www/lib/main.server.options.dart
@@ -30,6 +30,8 @@ import 'package:site_shared/components/common/client/collapse_button.dart'
     as _collapse_button;
 import 'package:site_shared/components/common/client/copy_button.dart'
     as _copy_button;
+import 'package:site_shared/components/common/client/mermaid_diagram.dart'
+    as _mermaid_diagram;
 import 'package:site_shared/components/dartpad/dartpad_injector.dart'
     as _dartpad_injector;
 
@@ -103,6 +105,11 @@ ServerOptions get defaultServerOptions => ServerOptions(
       'site_shared:copy_button',
       params: __copy_buttonCopyButton,
     ),
+    _mermaid_diagram.MermaidViewer:
+        ClientTarget<_mermaid_diagram.MermaidViewer>(
+          'site_shared:mermaid_diagram',
+          params: __mermaid_diagramMermaidViewer,
+        ),
     _dartpad_injector.DartPadInjector:
         ClientTarget<_dartpad_injector.DartPadInjector>(
           'site_shared:dartpad_injector',
@@ -159,6 +166,9 @@ Map __copy_buttonCopyButton(_copy_button.CopyButton c) => {
   'classes': c.classes,
   'title': c.title,
 };
+Map __mermaid_diagramMermaidViewer(
+  _mermaid_diagram.MermaidViewer c,
+) => {'diagram': c.diagram};
 Map __dartpad_injectorDartPadInjector(
   _dartpad_injector.DartPadInjector c,
 ) => {

From ecd987c2d090d49c71c639f458b492d506a242e3 Mon Sep 17 00:00:00 2001
From: Eric Windmill 
Date: Wed, 19 Aug 2026 08:19:31 -0700
Subject: [PATCH 4/4] checkin

---
 packages/site_shared/pubspec.yaml  |  1 -
 sites/docs/src/content/ai/evals.md | 22 ++++++++++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/packages/site_shared/pubspec.yaml b/packages/site_shared/pubspec.yaml
index d0a85ca7fcd..a43473195f7 100644
--- a/packages/site_shared/pubspec.yaml
+++ b/packages/site_shared/pubspec.yaml
@@ -31,4 +31,3 @@ dependencies:
 
 dev_dependencies:
   analysis_defaults: any
-  test: ^1.31.1
diff --git a/sites/docs/src/content/ai/evals.md b/sites/docs/src/content/ai/evals.md
index bc6767f2778..e8b56a5e769 100644
--- a/sites/docs/src/content/ai/evals.md
+++ b/sites/docs/src/content/ai/evals.md
@@ -44,4 +44,26 @@ flowchart TB
     one --> two
     three --> two
     two --> c2
+```
+
+More test
+
+```mermaid
+flowchart LR
+  subgraph tasks
+    direction TB
+    t1(Do first thing) --> t2(do second thing)
+  end 
+  User["`As an **App developer**`"] -->|i want to| Goal[fix overflow errors]
+  Goal-->tasks
+```
+
+
+```mermaid
+graph TD
+    A[Start] --> B{Is it working?}
+    B -->|Yes| C[Ship it]
+    B -->|No| D[Debug]
+    D --> B
+    C --> E[Celebrate]
 ```
\ No newline at end of file