diff --git a/docs/ABSTRACTIONS.md b/docs/ABSTRACTIONS.md
index 40d0b586..62aff0ad 100644
--- a/docs/ABSTRACTIONS.md
+++ b/docs/ABSTRACTIONS.md
@@ -509,6 +509,15 @@ Defined in `src/utils/mathMarkdown.ts`, `src/components/editorSchema.tsx`, and s
- `serializeMathAwareBlocks()` converts math nodes back to Markdown delimiters before save, raw-mode entry, and editor-position snapshots.
- Raw CodeMirror mode always shows the plain Markdown source, so imported technical notes stay editable outside Tolaria.
+### Mermaid Diagrams
+
+Defined in `src/utils/mermaidMarkdown.ts`, `src/components/MermaidDiagram.tsx`, `src/components/editorSchema.tsx`, and styled in `src/components/EditorTheme.css`:
+
+- Fenced `mermaid` blocks become `mermaidBlock` schema nodes before BlockNote sees the Markdown body.
+- Each `mermaidBlock` stores the original fenced Markdown plus the diagram body, so raw-mode entry and saves can restore the canonical source instead of serializing generated SVG.
+- The rich editor renders diagrams with the `mermaid` package and uses the original source as an inline fallback when rendering fails.
+- `serializeMermaidAwareBlocks()` wraps the math-aware serializer so math, wikilinks, and diagrams share the same Markdown-first save path.
+
### Formatting Surface Policy
Defined in `src/components/tolariaEditorFormatting.tsx` and `src/components/tolariaEditorFormattingConfig.ts`:
@@ -527,24 +536,25 @@ Defined in `src/components/tolariaEditorFormatting.tsx` and `src/components/tola
```mermaid
flowchart LR
A["π Raw markdown\n(from disk)"] --> B["splitFrontmatter()\nβ yaml + body"]
- B --> C["preProcessWikilinks(body)\n[[target]] β βΉtokenβΊ"]
- C --> D["preProcessMathMarkdown(body)\n$...$ / $$...$$ β tokens"]
- D --> E["tryParseMarkdownToBlocks()\nβ BlockNote block tree"]
- E --> F["injectWikilinks + injectMathInBlocks\n tokens β schema nodes"]
- F --> G["editor.replaceBlocks()\nβ rendered editor"]
+ B --> C["preProcessMermaidMarkdown(body)\nmermaid fence β token"]
+ C --> D["preProcessWikilinks(body)\n[[target]] β βΉtokenβΊ"]
+ D --> E["preProcessMathMarkdown(body)\n$...$ / $$...$$ β tokens"]
+ E --> F["tryParseMarkdownToBlocks()\nβ BlockNote block tree"]
+ F --> G["injectWikilinks + injectMathInBlocks + injectMermaidInBlocks\n tokens β schema nodes"]
+ G --> H["editor.replaceBlocks()\nβ rendered editor"]
style A fill:#f8f9fa,stroke:#6c757d,color:#000
- style G fill:#d4edda,stroke:#28a745,color:#000
+ style H fill:#d4edda,stroke:#28a745,color:#000
```
-> Wikilink placeholder tokens use `\u2039` and `\u203A`; math placeholder tokens use ASCII sentinels with URI-encoded LaTeX payloads.
+> Wikilink placeholder tokens use `\u2039` and `\u203A`; math and Mermaid placeholder tokens use ASCII sentinels with URI-encoded payloads.
### BlockNote-to-Markdown Pipeline (Save)
```mermaid
flowchart LR
A["βοΈ BlockNote blocks\n(editor state)"] --> B["blocksToMarkdownLossy()"]
- B --> C["restoreWikilinks + serializeMathAwareBlocks()\nschema nodes β Markdown source"]
+ B --> C["restoreWikilinks + serializeMermaidAwareBlocks()\nschema nodes β Markdown source"]
C --> D["prepend frontmatter yaml"]
D --> E["invoke('save_note_content')\nβ disk write"]
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index c468bc27..22ede24f 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -91,6 +91,7 @@ flowchart LR
| Frontend | React + TypeScript | React 19, TS 5.9 |
| Editor | BlockNote | 0.46.2 |
| Code block highlighting | @blocknote/code-block | 0.46.2 |
+| Diagram rendering | Mermaid | 11.14.0 |
| Raw editor | CodeMirror 6 | - |
| Styling | Tailwind CSS v4 + CSS variables | 4.1.18 |
| UI primitives | Radix UI + shadcn/ui | - |
@@ -180,7 +181,7 @@ flowchart TD
- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Changes, Pulse), collapsible type-based section groups, and a dedicated folder tree. The folder tree shows user-created folders plus default vault folders such as `attachments/` and `views/`; only the dedicated `type/` directory stays hidden because note types already have their own sidebar section. The folder tree supports inline folder creation and rename, exposes a right-click menu for rename/delete plus filesystem reveal/copy-path actions, and auto-expands ancestor folders when the current selection or rename target is nested. Type sections and folder rows also act as note drop targets: dropping a note on a type updates its `type:` frontmatter, while dropping it on a folder runs the same crash-safe move path as the command palette flow. Each type can have a custom icon, color, sort, and visibility set via its type document in `type/`.
- **Note List / Pulse View** (200-500px, resizable): When a section group, filter, or saved view is selected, shows filtered notes with snippets, modified dates, status indicators, and per-context note-list controls. When `selection.kind === 'entity'`, the same pane enters **Neighborhood** mode: the source note is pinned at the top as a normal active row, outgoing relationship groups render first, inverse/backlink groups follow, empty groups stay visible with `0`, and duplicates across groups are allowed when multiple relationships are true. Plain click / `Enter` open the focused note without replacing the current Neighborhood, while Cmd/Ctrl-click and Cmd/Ctrl-`Enter` pivot the pane into the clicked note's Neighborhood. Folder-backed lists also show non-Markdown files: previewable image binaries get an image indicator and open in the editor pane, while unsupported binaries remain muted instead of auto-launching an external app. Saved views reuse the same sort and visible-column controls as the built-in lists, and those changes persist back into the view `.yml` definition (`sort`, `listPropertiesDisplay`). When Pulse filter is active, shows `PulseView` β a chronological git activity feed grouped by day.
-- **Editor** (flex, fills remaining space): Single note open at a time (no tabs β see ADR-0003). Breadcrumb bar with word count and note-layout toggle, BlockNote rich text editor with wikilink support, Markdown-compatible inline/display math rendering, markdown-safe formatting controls, and schema-backed fenced code block highlighting via `@blocknote/code-block`. Can toggle to diff view (modified files), raw CodeMirror view, or a wide-screen left-aligned note column while preserving the same readable max width. Binary image files render through `FilePreview` as ordinary vault files using Tauri asset URLs, with explicit unsupported/broken fallback states and keyboard focus returning to the note list on `Escape`. Decomposed into `Editor` (orchestrator), `EditorContent`, `FilePreview`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, and `useEditorSave`, plus the `useRawMode`/`RawEditorView` pair for markdown source editing. Rich BlockNote input and raw CodeMirror input both route typed `->`, `<-`, and `<->` through the shared `src/utils/arrowLigatures.ts` resolver so arrow ligatures stay consistent across mode switches while escaped ASCII sequences remain literal. Navigation history (Cmd+[/]) replaces tabs.
+- **Editor** (flex, fills remaining space): Single note open at a time (no tabs β see ADR-0003). Breadcrumb bar with word count and note-layout toggle, BlockNote rich text editor with wikilink support, Markdown-compatible inline/display math rendering, first-class Mermaid diagram blocks, markdown-safe formatting controls, and schema-backed fenced code block highlighting via `@blocknote/code-block`. Can toggle to diff view (modified files), raw CodeMirror view, or a wide-screen left-aligned note column while preserving the same readable max width. Binary image files render through `FilePreview` as ordinary vault files using Tauri asset URLs, with explicit unsupported/broken fallback states and keyboard focus returning to the note list on `Escape`. Decomposed into `Editor` (orchestrator), `EditorContent`, `FilePreview`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, and `useEditorSave`, plus the `useRawMode`/`RawEditorView` pair for markdown source editing. Rich BlockNote input and raw CodeMirror input both route typed `->`, `<-`, and `<->` through the shared `src/utils/arrowLigatures.ts` resolver so arrow ligatures stay consistent across mode switches while escaped ASCII sequences remain literal. Navigation history (Cmd+[/]) replaces tabs.
- **Inspector / AI Agent** (200-500px or 40px collapsed): Toggles between Inspector (frontmatter, relationships, instances, backlinks, git history) and AI Agent panel (the selected CLI agent with tool execution). The Sparkle icon in the breadcrumb bar toggles between them. Per-note `icon` is a suggested Inspector property and the command palette's "Set Note Icon" action opens that field directly. When viewing a Type note, the Inspector shows an **Instances** section listing all notes of that type (sorted by modified_at desc, capped at 50).
Panels are separated by `ResizeHandle` components that support drag-to-resize.
diff --git a/docs/adr/0088-markdown-durable-mermaid-diagrams.md b/docs/adr/0088-markdown-durable-mermaid-diagrams.md
new file mode 100644
index 00000000..6aaeeb60
--- /dev/null
+++ b/docs/adr/0088-markdown-durable-mermaid-diagrams.md
@@ -0,0 +1,40 @@
+---
+type: ADR
+id: "0088"
+title: "Markdown-durable Mermaid diagrams in notes"
+status: active
+date: 2026-04-27
+---
+
+## Context
+
+Tolaria notes are plain Markdown files, while the rich editor uses BlockNote and raw mode uses CodeMirror. Users need fenced `mermaid` blocks to render as diagrams in the note surface without changing the canonical file format or hiding the source from raw editing.
+
+BlockNote can parse fenced code blocks, but a generic highlighted code block does not provide diagram rendering. Rendering Mermaid directly from the Markdown fence also has to preserve the original fence source when notes are saved, copied through raw mode, closed, and reopened.
+
+## Decision
+
+**Tolaria will support Mermaid diagrams through a Markdown placeholder round-trip owned by the editor pipeline and rendered with the `mermaid` package.**
+
+The implementation:
+
+- Converts fenced `mermaid` blocks to temporary placeholders before BlockNote parses Markdown.
+- Replaces placeholders with a `mermaidBlock` schema block that stores both the original fenced source and the diagram body.
+- Renders the block through Mermaid in the rich editor.
+- Serializes `mermaidBlock` nodes back to their stored fenced Markdown before save, raw-mode entry, and editor-position snapshots.
+- Shows the original source as an inline fallback when Mermaid cannot render a diagram.
+
+## Options considered
+
+- **Tolaria-owned placeholder round-trip with Mermaid rendering** (chosen): matches the existing wikilink and math architecture, keeps Markdown as the source of truth, and gives Tolaria explicit control over serialization.
+- **Render all `mermaid` code blocks by overriding the generic code-block renderer**: smaller surface, but it couples diagram behavior to the code-highlighting package and makes exact source preservation harder.
+- **Raw-mode-only Mermaid support**: preserves source but fails the enhanced note reading experience users expect.
+- **Store parsed diagram metadata outside the Markdown body**: enables richer future editing, but violates the files-first model.
+
+## Consequences
+
+- `src/utils/mermaidMarkdown.ts` is the canonical parser/serializer bridge for note diagrams.
+- Rich mode renders diagrams as schema-backed blocks; raw mode remains the direct source editor.
+- Invalid Mermaid source remains visible instead of breaking the editor surface.
+- `mermaid` is now a runtime dependency and should be upgraded deliberately with rendering regression coverage.
+- Future diagram controls, such as copy source or expand, can attach to the same `mermaidBlock` without changing storage.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 2facd9d4..46be83e0 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -140,3 +140,4 @@ proposed β active β superseded
| [0082](0082-markdown-durable-math-notes.md) | Markdown-durable math in notes | active |
| [0083](0083-dual-architecture-macos-release-artifacts.md) | Dual-architecture macOS release artifacts | active |
| [0085](0085-non-git-vault-support.md) | Non-git vaults open with explicit later Git initialization | active |
+| [0088](0088-markdown-durable-mermaid-diagrams.md) | Markdown-durable Mermaid diagrams in notes | active |
diff --git a/package.json b/package.json
index 46438ca7..203a39c8 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"date-fns": "^4.1.0",
"katex": "^0.16.28",
"lucide-react": "^0.564.0",
+ "mermaid": "^11.14.0",
"posthog-js": "^1.363.5",
"radix-ui": "^1.4.3",
"react": "^19.2.0",
@@ -75,12 +76,12 @@
"unicode-emoji-json": "^0.8.0"
},
"devDependencies": {
- "@translated/lara-cli": "^1.3.2",
"@eslint/js": "^9.39.1",
"@playwright/test": "^1.58.2",
"@tauri-apps/cli": "^2.10.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
+ "@translated/lara-cli": "^1.3.2",
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1fed9494..e6b006e9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -130,6 +130,9 @@ importers:
lucide-react:
specifier: ^0.564.0
version: 0.564.0(react@19.2.4)
+ mermaid:
+ specifier: ^11.14.0
+ version: 11.14.0
posthog-js:
specifier: ^1.363.5
version: 1.363.5
@@ -266,6 +269,9 @@ packages:
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+ '@antfu/install-pkg@1.1.0':
+ resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
+
'@anthropic-ai/sdk@0.78.0':
resolution: {integrity: sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w==}
hasBin: true
@@ -408,6 +414,24 @@ packages:
react: ^18.0 || ^19.0 || >= 19.0.0-rc
react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc
+ '@braintree/sanitize-url@7.1.2':
+ resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
+
+ '@chevrotain/cst-dts-gen@12.0.0':
+ resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==}
+
+ '@chevrotain/gast@12.0.0':
+ resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==}
+
+ '@chevrotain/regexp-to-ast@12.0.0':
+ resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==}
+
+ '@chevrotain/types@12.0.0':
+ resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==}
+
+ '@chevrotain/utils@12.0.0':
+ resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==}
+
'@codemirror/autocomplete@6.20.1':
resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
@@ -753,6 +777,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@iconify/types@2.0.0':
+ resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+
+ '@iconify/utils@3.1.1':
+ resolution: {integrity: sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==}
+
'@inquirer/ansi@1.0.2':
resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==}
engines: {node: '>=18'}
@@ -951,6 +981,9 @@ packages:
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+ '@mermaid-js/parser@1.1.0':
+ resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==}
+
'@modelcontextprotocol/sdk@1.27.1':
resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==}
engines: {node: '>=18'}
@@ -1849,66 +1882,79 @@ packages:
resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.57.1':
resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.57.1':
resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.57.1':
resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.57.1':
resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.57.1':
resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
cpu: [loong64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.57.1':
resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.57.1':
resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
cpu: [ppc64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.57.1':
resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.57.1':
resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
cpu: [riscv64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.57.1':
resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.57.1':
resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.57.1':
resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-openbsd-x64@4.57.1':
resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
@@ -2036,24 +2082,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
@@ -2123,30 +2173,35 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.10.0':
resolution: {integrity: sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@tauri-apps/cli-linux-riscv64-gnu@2.10.0':
resolution: {integrity: sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-x64-gnu@2.10.0':
resolution: {integrity: sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.10.0':
resolution: {integrity: sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.10.0':
resolution: {integrity: sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==}
@@ -2317,6 +2372,99 @@ packages:
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+ '@types/d3-array@3.2.2':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/d3-axis@3.0.6':
+ resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+ '@types/d3-brush@3.0.6':
+ resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+ '@types/d3-chord@3.0.6':
+ resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-contour@3.0.6':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/d3-delaunay@6.0.4':
+ resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+ '@types/d3-dispatch@3.0.7':
+ resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-dsv@3.0.7':
+ resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-fetch@3.0.7':
+ resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+ '@types/d3-format@3.0.4':
+ resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+ '@types/d3-geo@3.1.0':
+ resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+ '@types/d3-hierarchy@3.1.7':
+ resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-polygon@3.0.2':
+ resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+ '@types/d3-quadtree@3.0.6':
+ resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+ '@types/d3-random@3.0.3':
+ resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+ '@types/d3-scale-chromatic@3.1.0':
+ resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-shape@3.1.8':
+ resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
+
+ '@types/d3-time-format@4.0.3':
+ resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -2329,6 +2477,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -2441,6 +2592,9 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@upsetjs/venn.js@2.0.0':
+ resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
+
'@vitejs/plugin-react@5.1.4':
resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2503,6 +2657,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
@@ -2655,6 +2814,15 @@ packages:
chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
+ chevrotain-allstar@0.4.1:
+ resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==}
+ peerDependencies:
+ chevrotain: ^12.0.0
+
+ chevrotain@12.0.0:
+ resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==}
+ engines: {node: '>=22.0.0'}
+
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -2692,6 +2860,10 @@ packages:
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
engines: {node: '>=20'}
+ commander@7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
@@ -2699,6 +2871,9 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
content-disposition@1.0.1:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
@@ -2725,6 +2900,12 @@ packages:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
+ cose-base@1.0.3:
+ resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
+ cose-base@2.2.0:
+ resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
+
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
@@ -2746,6 +2927,162 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ cytoscape-cose-bilkent@4.1.0:
+ resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape-fcose@2.2.0:
+ resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape@3.33.2:
+ resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==}
+ engines: {node: '>=0.10'}
+
+ d3-array@2.12.1:
+ resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-axis@3.0.0:
+ resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+ engines: {node: '>=12'}
+
+ d3-brush@3.0.0:
+ resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+ engines: {node: '>=12'}
+
+ d3-chord@3.0.1:
+ resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-contour@4.0.2:
+ resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+ engines: {node: '>=12'}
+
+ d3-delaunay@6.0.4:
+ resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-dsv@3.0.1:
+ resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-fetch@3.0.1:
+ resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+ engines: {node: '>=12'}
+
+ d3-force@3.0.0:
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.2:
+ resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
+ engines: {node: '>=12'}
+
+ d3-geo@3.1.1:
+ resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+ engines: {node: '>=12'}
+
+ d3-hierarchy@3.1.2:
+ resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@1.0.9:
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-polygon@3.0.1:
+ resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+ engines: {node: '>=12'}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-random@3.0.1:
+ resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+ engines: {node: '>=12'}
+
+ d3-sankey@0.12.3:
+ resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+ d3-scale-chromatic@3.1.0:
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@1.3.7:
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ d3@7.9.0:
+ resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+ engines: {node: '>=12'}
+
+ dagre-d3-es@7.0.14:
+ resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}
+
data-urls@7.0.0:
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -2756,6 +3093,9 @@ packages:
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+ dayjs@1.11.20:
+ resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
+
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -2774,6 +3114,9 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ delaunator@5.1.0:
+ resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -3132,6 +3475,9 @@ packages:
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
engines: {node: '>=6.0'}
+ hachure-fill@0.5.2:
+ resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -3283,6 +3629,13 @@ packages:
inline-style-parser@0.2.7:
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+ internmap@1.0.1:
+ resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
ip-address@10.0.1:
resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}
engines: {node: '>= 12'}
@@ -3429,10 +3782,23 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ khroma@2.1.0:
+ resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
+
kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
+ langium@4.2.2:
+ resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==}
+ engines: {node: '>=20.10.0', npm: '>=10.2.3'}
+
+ layout-base@1.0.2:
+ resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+ layout-base@2.0.1:
+ resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
+
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -3477,24 +3843,28 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-arm64-musl@1.30.2:
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
lightningcss-linux-x64-gnu@1.30.2:
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-x64-musl@1.30.2:
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [musl]
lightningcss-win32-arm64-msvc@1.30.2:
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
@@ -3522,6 +3892,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash-es@4.18.1:
+ resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
+
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -3571,6 +3944,11 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+ marked@16.4.2:
+ resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -3634,6 +4012,9 @@ packages:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
+ mermaid@11.14.0:
+ resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==}
+
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
@@ -3757,6 +4138,9 @@ packages:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
+ mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -3829,6 +4213,9 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+ package-manager-detector@1.6.0:
+ resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -3846,6 +4233,9 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
+ path-data-parser@0.1.0:
+ resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -3883,6 +4273,9 @@ packages:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
playwright-core@1.58.2:
resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==}
engines: {node: '>=18'}
@@ -3893,6 +4286,12 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ points-on-curve@0.2.0:
+ resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
+
+ points-on-path@0.2.1:
+ resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
+
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@@ -4198,6 +4597,9 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
+ robust-predicates@3.0.3:
+ resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
+
rollup@4.57.1:
resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4206,10 +4608,16 @@ packages:
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+ roughjs@4.6.6:
+ resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
+ rw@1.3.3:
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -4352,6 +4760,9 @@ packages:
style-to-object@1.0.14:
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+ stylis@4.4.0:
+ resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -4424,6 +4835,10 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
+ ts-dedent@2.2.0:
+ resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
+ engines: {node: '>=6.10'}
+
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -4457,6 +4872,9 @@ packages:
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+ ufo@1.6.3:
+ resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
+
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -4553,6 +4971,10 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -4644,6 +5066,26 @@ packages:
jsdom:
optional: true
+ vscode-jsonrpc@8.2.0:
+ resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
+ engines: {node: '>=14.0.0'}
+
+ vscode-languageserver-protocol@3.17.5:
+ resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
+
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+ vscode-languageserver-types@3.17.5:
+ resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
+
+ vscode-languageserver@9.0.1:
+ resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
+ hasBin: true
+
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
@@ -4768,6 +5210,11 @@ snapshots:
'@adobe/css-tools@4.4.4': {}
+ '@antfu/install-pkg@1.1.0':
+ dependencies:
+ package-manager-detector: 1.6.0
+ tinyexec: 1.0.2
+
'@anthropic-ai/sdk@0.78.0(zod@4.3.6)':
dependencies:
json-schema-to-ts: 3.1.1
@@ -5024,6 +5471,23 @@ snapshots:
- sugar-high
- supports-color
+ '@braintree/sanitize-url@7.1.2': {}
+
+ '@chevrotain/cst-dts-gen@12.0.0':
+ dependencies:
+ '@chevrotain/gast': 12.0.0
+ '@chevrotain/types': 12.0.0
+
+ '@chevrotain/gast@12.0.0':
+ dependencies:
+ '@chevrotain/types': 12.0.0
+
+ '@chevrotain/regexp-to-ast@12.0.0': {}
+
+ '@chevrotain/types@12.0.0': {}
+
+ '@chevrotain/utils@12.0.0': {}
+
'@codemirror/autocomplete@6.20.1':
dependencies:
'@codemirror/language': 6.12.2
@@ -5339,6 +5803,14 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@iconify/types@2.0.0': {}
+
+ '@iconify/utils@3.1.1':
+ dependencies:
+ '@antfu/install-pkg': 1.1.0
+ '@iconify/types': 2.0.0
+ mlly: 1.8.2
+
'@inquirer/ansi@1.0.2': {}
'@inquirer/checkbox@4.3.2(@types/node@24.10.13)':
@@ -5548,6 +6020,10 @@ snapshots:
'@marijn/find-cluster-break@1.0.2': {}
+ '@mermaid-js/parser@1.1.0':
+ dependencies:
+ langium: 4.2.2
+
'@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)':
dependencies:
'@hono/node-server': 1.19.9(hono@4.12.3)
@@ -6941,6 +7417,123 @@ snapshots:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-chord@3.0.6': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-contour@3.0.6':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-delaunay@6.0.4': {}
+
+ '@types/d3-dispatch@3.0.7': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-dsv@3.0.7': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-fetch@3.0.7':
+ dependencies:
+ '@types/d3-dsv': 3.0.7
+
+ '@types/d3-force@3.0.10': {}
+
+ '@types/d3-format@3.0.4': {}
+
+ '@types/d3-geo@3.1.0':
+ dependencies:
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-hierarchy@3.1.7': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-polygon@3.0.2': {}
+
+ '@types/d3-quadtree@3.0.6': {}
+
+ '@types/d3-random@3.0.3': {}
+
+ '@types/d3-scale-chromatic@3.1.0': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-shape@3.1.8':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time-format@4.0.3': {}
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3@7.4.3':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-axis': 3.0.6
+ '@types/d3-brush': 3.0.6
+ '@types/d3-chord': 3.0.6
+ '@types/d3-color': 3.1.3
+ '@types/d3-contour': 3.0.6
+ '@types/d3-delaunay': 6.0.4
+ '@types/d3-dispatch': 3.0.7
+ '@types/d3-drag': 3.0.7
+ '@types/d3-dsv': 3.0.7
+ '@types/d3-ease': 3.0.2
+ '@types/d3-fetch': 3.0.7
+ '@types/d3-force': 3.0.10
+ '@types/d3-format': 3.0.4
+ '@types/d3-geo': 3.1.0
+ '@types/d3-hierarchy': 3.1.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-path': 3.1.1
+ '@types/d3-polygon': 3.0.2
+ '@types/d3-quadtree': 3.0.6
+ '@types/d3-random': 3.0.3
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.11
+ '@types/d3-shape': 3.1.8
+ '@types/d3-time': 3.0.4
+ '@types/d3-time-format': 4.0.3
+ '@types/d3-timer': 3.0.2
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -6953,6 +7546,8 @@ snapshots:
'@types/estree@1.0.8': {}
+ '@types/geojson@7946.0.16': {}
+
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -7094,6 +7689,11 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
+ '@upsetjs/venn.js@2.0.0':
+ optionalDependencies:
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
'@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.3))':
dependencies:
'@babel/core': 7.29.0
@@ -7174,6 +7774,8 @@ snapshots:
acorn@8.15.0: {}
+ acorn@8.16.0: {}
+
agent-base@7.1.4: {}
ajv-formats@3.0.1(ajv@8.18.0):
@@ -7321,6 +7923,19 @@ snapshots:
chardet@2.1.1: {}
+ chevrotain-allstar@0.4.1(chevrotain@12.0.0):
+ dependencies:
+ chevrotain: 12.0.0
+ lodash-es: 4.18.1
+
+ chevrotain@12.0.0:
+ dependencies:
+ '@chevrotain/cst-dts-gen': 12.0.0
+ '@chevrotain/gast': 12.0.0
+ '@chevrotain/regexp-to-ast': 12.0.0
+ '@chevrotain/types': 12.0.0
+ '@chevrotain/utils': 12.0.0
+
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -7349,10 +7964,14 @@ snapshots:
commander@14.0.3: {}
+ commander@7.2.0: {}
+
commander@8.3.0: {}
concat-map@0.0.1: {}
+ confbox@0.1.8: {}
+
content-disposition@1.0.1: {}
content-type@1.0.5: {}
@@ -7370,6 +7989,14 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
+ cose-base@1.0.3:
+ dependencies:
+ layout-base: 1.0.2
+
+ cose-base@2.2.0:
+ dependencies:
+ layout-base: 2.0.1
+
crelt@1.0.6: {}
cross-spawn@7.0.6:
@@ -7394,6 +8021,190 @@ snapshots:
csstype@3.2.3: {}
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2):
+ dependencies:
+ cose-base: 1.0.3
+ cytoscape: 3.33.2
+
+ cytoscape-fcose@2.2.0(cytoscape@3.33.2):
+ dependencies:
+ cose-base: 2.2.0
+ cytoscape: 3.33.2
+
+ cytoscape@3.33.2: {}
+
+ d3-array@2.12.1:
+ dependencies:
+ internmap: 1.0.1
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-axis@3.0.0: {}
+
+ d3-brush@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3-chord@3.0.1:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-color@3.1.0: {}
+
+ d3-contour@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-delaunay@6.0.4:
+ dependencies:
+ delaunator: 5.1.0
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-dsv@3.0.1:
+ dependencies:
+ commander: 7.2.0
+ iconv-lite: 0.6.3
+ rw: 1.3.3
+
+ d3-ease@3.0.1: {}
+
+ d3-fetch@3.0.1:
+ dependencies:
+ d3-dsv: 3.0.1
+
+ d3-force@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+ d3-format@3.1.2: {}
+
+ d3-geo@3.1.1:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-hierarchy@3.1.2: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@1.0.9: {}
+
+ d3-path@3.1.0: {}
+
+ d3-polygon@3.0.1: {}
+
+ d3-quadtree@3.0.1: {}
+
+ d3-random@3.0.1: {}
+
+ d3-sankey@0.12.3:
+ dependencies:
+ d3-array: 2.12.1
+ d3-shape: 1.3.7
+
+ d3-scale-chromatic@3.1.0:
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-shape@1.3.7:
+ dependencies:
+ d3-path: 1.0.9
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3@7.9.0:
+ dependencies:
+ d3-array: 3.2.4
+ d3-axis: 3.0.0
+ d3-brush: 3.0.0
+ d3-chord: 3.0.1
+ d3-color: 3.1.0
+ d3-contour: 4.0.2
+ d3-delaunay: 6.0.4
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-dsv: 3.0.1
+ d3-ease: 3.0.1
+ d3-fetch: 3.0.1
+ d3-force: 3.0.0
+ d3-format: 3.1.2
+ d3-geo: 3.1.1
+ d3-hierarchy: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-path: 3.1.0
+ d3-polygon: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-random: 3.0.1
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ d3-selection: 3.0.0
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+ d3-timer: 3.0.1
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+ d3-zoom: 3.0.0
+
+ dagre-d3-es@7.0.14:
+ dependencies:
+ d3: 7.9.0
+ lodash-es: 4.18.1
+
data-urls@7.0.0:
dependencies:
whatwg-mimetype: 5.0.0
@@ -7405,6 +8216,8 @@ snapshots:
date-fns@4.1.0: {}
+ dayjs@1.11.20: {}
+
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -7417,6 +8230,10 @@ snapshots:
deep-is@0.1.4: {}
+ delaunator@5.1.0:
+ dependencies:
+ robust-predicates: 3.0.3
+
delayed-stream@1.0.0: {}
depd@2.0.0: {}
@@ -7815,6 +8632,8 @@ snapshots:
section-matter: 1.0.0
strip-bom-string: 1.0.0
+ hachure-fill@0.5.2: {}
+
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
@@ -8045,6 +8864,10 @@ snapshots:
inline-style-parser@0.2.7: {}
+ internmap@1.0.1: {}
+
+ internmap@2.0.3: {}
+
ip-address@10.0.1: {}
ipaddr.js@1.9.1: {}
@@ -8173,8 +8996,23 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ khroma@2.1.0: {}
+
kind-of@6.0.3: {}
+ langium@4.2.2:
+ dependencies:
+ '@chevrotain/regexp-to-ast': 12.0.0
+ chevrotain: 12.0.0
+ chevrotain-allstar: 0.4.1(chevrotain@12.0.0)
+ vscode-languageserver: 9.0.1
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-uri: 3.1.0
+
+ layout-base@1.0.2: {}
+
+ layout-base@2.0.1: {}
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -8243,6 +9081,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash-es@4.18.1: {}
+
lodash.merge@4.6.2: {}
log-symbols@6.0.0:
@@ -8297,6 +9137,8 @@ snapshots:
markdown-table@3.0.4: {}
+ marked@16.4.2: {}
+
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
@@ -8460,6 +9302,30 @@ snapshots:
merge-descriptors@2.0.0: {}
+ mermaid@11.14.0:
+ dependencies:
+ '@braintree/sanitize-url': 7.1.2
+ '@iconify/utils': 3.1.1
+ '@mermaid-js/parser': 1.1.0
+ '@types/d3': 7.4.3
+ '@upsetjs/venn.js': 2.0.0
+ cytoscape: 3.33.2
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2)
+ cytoscape-fcose: 2.2.0(cytoscape@3.33.2)
+ d3: 7.9.0
+ d3-sankey: 0.12.3
+ dagre-d3-es: 7.0.14
+ dayjs: 1.11.20
+ dompurify: 3.3.3
+ katex: 0.16.28
+ khroma: 2.1.0
+ lodash-es: 4.18.1
+ marked: 16.4.2
+ roughjs: 4.6.6
+ stylis: 4.4.0
+ ts-dedent: 2.2.0
+ uuid: 11.1.0
+
micromark-core-commonmark@2.0.3:
dependencies:
decode-named-character-reference: 1.3.0
@@ -8681,6 +9547,13 @@ snapshots:
minipass@7.1.3: {}
+ mlly@1.8.2:
+ dependencies:
+ acorn: 8.16.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.3
+
ms@2.1.3: {}
mute-stream@2.0.0: {}
@@ -8752,6 +9625,8 @@ snapshots:
package-json-from-dist@1.0.1: {}
+ package-manager-detector@1.6.0: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -8776,6 +9651,8 @@ snapshots:
parseurl@1.3.3: {}
+ path-data-parser@0.1.0: {}
+
path-exists@4.0.0: {}
path-expression-matcher@1.5.0: {}
@@ -8799,6 +9676,12 @@ snapshots:
pkce-challenge@5.0.1: {}
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.2
+ pathe: 2.0.3
+
playwright-core@1.58.2: {}
playwright@1.58.2:
@@ -8807,6 +9690,13 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
+ points-on-curve@0.2.0: {}
+
+ points-on-path@0.2.1:
+ dependencies:
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@@ -9258,6 +10148,8 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
+ robust-predicates@3.0.3: {}
+
rollup@4.57.1:
dependencies:
'@types/estree': 1.0.8
@@ -9291,6 +10183,13 @@ snapshots:
rope-sequence@1.3.4: {}
+ roughjs@4.6.6:
+ dependencies:
+ hachure-fill: 0.5.2
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+ points-on-path: 0.2.1
+
router@2.2.0:
dependencies:
debug: 4.4.3
@@ -9301,6 +10200,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ rw@1.3.3: {}
+
safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {}
@@ -9453,6 +10354,8 @@ snapshots:
dependencies:
inline-style-parser: 0.2.7
+ stylis@4.4.0: {}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -9506,6 +10409,8 @@ snapshots:
dependencies:
typescript: 5.9.3
+ ts-dedent@2.2.0: {}
+
tslib@2.8.1: {}
tw-animate-css@1.4.0: {}
@@ -9537,6 +10442,8 @@ snapshots:
uc.micro@2.1.0: {}
+ ufo@1.6.3: {}
+
undici-types@7.16.0: {}
undici@7.22.0: {}
@@ -9631,6 +10538,8 @@ snapshots:
dependencies:
react: 19.2.4
+ uuid@11.1.0: {}
+
uuid@8.3.2: {}
vary@1.1.2: {}
@@ -9704,6 +10613,23 @@ snapshots:
- tsx
- yaml
+ vscode-jsonrpc@8.2.0: {}
+
+ vscode-languageserver-protocol@3.17.5:
+ dependencies:
+ vscode-jsonrpc: 8.2.0
+ vscode-languageserver-types: 3.17.5
+
+ vscode-languageserver-textdocument@1.0.12: {}
+
+ vscode-languageserver-types@3.17.5: {}
+
+ vscode-languageserver@9.0.1:
+ dependencies:
+ vscode-languageserver-protocol: 3.17.5
+
+ vscode-uri@3.1.0: {}
+
w3c-keyname@2.2.8: {}
w3c-xmlserializer@5.0.0:
diff --git a/src/components/EditorTheme.css b/src/components/EditorTheme.css
index 62c39297..5b926de8 100644
--- a/src/components/EditorTheme.css
+++ b/src/components/EditorTheme.css
@@ -268,6 +268,59 @@
white-space: pre-wrap;
}
+.editor__blocknote-container .mermaid-diagram {
+ width: 100%;
+ margin: 10px 0;
+ color: var(--colors-text);
+}
+
+.editor__blocknote-container .mermaid-diagram__viewport {
+ max-width: 100%;
+ overflow: auto;
+ padding: 14px;
+ border: 1px solid var(--border-primary);
+ border-radius: 8px;
+ background: var(--surface-editor);
+}
+
+.editor__blocknote-container .mermaid-diagram__viewport:focus-visible {
+ outline: 2px solid var(--border-focus);
+ outline-offset: 2px;
+}
+
+.editor__blocknote-container .mermaid-diagram__viewport svg {
+ display: block;
+ max-width: none;
+ min-width: min-content;
+ height: auto;
+ margin: 0 auto;
+}
+
+.editor__blocknote-container .mermaid-diagram--error {
+ padding: 12px;
+ border: 1px solid var(--destructive);
+ border-radius: 8px;
+ background: color-mix(in srgb, var(--destructive) 8%, transparent);
+}
+
+.editor__blocknote-container .mermaid-diagram--error figcaption {
+ margin-bottom: 8px;
+ color: var(--destructive);
+ font-size: 0.85rem;
+ font-weight: 600;
+}
+
+.editor__blocknote-container .mermaid-diagram--error pre {
+ max-width: 100%;
+ overflow: auto;
+ margin: 0;
+ padding: 10px;
+ border-radius: 6px;
+ background: var(--inline-styles-code-background-color);
+ color: var(--inline-styles-code-color);
+ font-size: 0.85rem;
+}
+
.editor__blocknote-container[data-follow-links] .bn-editor a,
.editor__blocknote-container[data-follow-links] .wikilink {
cursor: pointer;
diff --git a/src/components/MermaidDiagram.test.tsx b/src/components/MermaidDiagram.test.tsx
new file mode 100644
index 00000000..7cb5b7b1
--- /dev/null
+++ b/src/components/MermaidDiagram.test.tsx
@@ -0,0 +1,49 @@
+import { render, screen, waitFor } from '@testing-library/react'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { MermaidDiagram } from './MermaidDiagram'
+
+const mermaidMock = vi.hoisted(() => ({
+ initialize: vi.fn(),
+ render: vi.fn(),
+}))
+
+vi.mock('mermaid', () => ({
+ default: mermaidMock,
+}))
+
+describe('MermaidDiagram', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mermaidMock.render.mockResolvedValue({
+ svg: '',
+ })
+ })
+
+ it('renders Mermaid SVG for valid source', async () => {
+ render(
+ B'}
+ source={'```mermaid\nflowchart LR\nA --> B\n```'}
+ />,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByTestId('mermaid-diagram-viewport').querySelector('svg')).not.toBeNull()
+ })
+ expect(mermaidMock.render).toHaveBeenCalledWith(expect.stringMatching(/^tolaria-mermaid-/), 'flowchart LR\nA --> B')
+ })
+
+ it('falls back to the original source when Mermaid cannot render', async () => {
+ mermaidMock.render.mockRejectedValueOnce(new Error('parse error'))
+
+ render(
+ ,
+ )
+
+ expect(await screen.findByText('Mermaid diagram unavailable')).toBeInTheDocument()
+ expect(screen.getByLabelText('Mermaid source')).toHaveTextContent('flowchart LR')
+ })
+})
diff --git a/src/components/MermaidDiagram.tsx b/src/components/MermaidDiagram.tsx
new file mode 100644
index 00000000..e2cd0804
--- /dev/null
+++ b/src/components/MermaidDiagram.tsx
@@ -0,0 +1,105 @@
+import { useEffect, useId, useMemo, useState } from 'react'
+
+type MermaidApi = typeof import('mermaid')['default']
+
+interface MermaidDiagramProps {
+ diagram: string
+ source: string
+}
+
+interface RenderState {
+ diagram: string
+ svg: string
+ error: boolean
+}
+
+let initialized = false
+let renderQueue = Promise.resolve()
+
+function renderIdFromReactId(reactId: string): string {
+ const safeId = reactId.replace(/[^a-zA-Z0-9_-]/g, '')
+ return `tolaria-mermaid-${safeId || 'diagram'}`
+}
+
+function initializeMermaid(mermaid: MermaidApi) {
+ if (initialized) return
+
+ mermaid.initialize({
+ startOnLoad: false,
+ securityLevel: 'strict',
+ theme: 'base',
+ themeVariables: {
+ background: '#ffffff',
+ primaryColor: '#f8fafc',
+ primaryTextColor: '#111827',
+ primaryBorderColor: '#94a3b8',
+ lineColor: '#64748b',
+ secondaryColor: '#f1f5f9',
+ tertiaryColor: '#e0f2fe',
+ fontFamily: 'ui-sans-serif, system-ui, sans-serif',
+ },
+ })
+ initialized = true
+}
+
+async function renderMermaidDiagram({
+ diagram,
+ renderId,
+}: {
+ diagram: string
+ renderId: string
+}): Promise {
+ const render = async () => {
+ const mermaid = (await import('mermaid')).default
+ initializeMermaid(mermaid)
+ const result = await mermaid.render(renderId, diagram)
+ return result.svg
+ }
+ const nextRender = renderQueue.then(render, render)
+ renderQueue = nextRender.then(() => undefined, () => undefined)
+ return nextRender
+}
+
+export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) {
+ const reactId = useId()
+ const renderId = useMemo(() => renderIdFromReactId(reactId), [reactId])
+ const [state, setState] = useState({ diagram: '', svg: '', error: false })
+
+ useEffect(() => {
+ let active = true
+ if (!diagram.trim()) return () => { active = false }
+
+ renderMermaidDiagram({ diagram, renderId })
+ .then((svg) => {
+ if (active) setState({ diagram, svg, error: false })
+ })
+ .catch(() => {
+ if (active) setState({ diagram, svg: '', error: true })
+ })
+
+ return () => { active = false }
+ }, [diagram, renderId])
+
+ const currentState = state.diagram === diagram ? state : { diagram, svg: '', error: false }
+ if (!diagram.trim() || currentState.error) {
+ return (
+
+ Mermaid diagram unavailable
+ {source}
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/editorModePosition.ts b/src/components/editorModePosition.ts
index 659ce3d9..2e2f138a 100644
--- a/src/components/editorModePosition.ts
+++ b/src/components/editorModePosition.ts
@@ -1,6 +1,6 @@
import { compactMarkdown } from '../utils/compact-markdown'
import { restoreWikilinksInBlocks, splitFrontmatter } from '../utils/wikilinks'
-import { serializeMathAwareBlocks } from '../utils/mathMarkdown'
+import { serializeMermaidAwareBlocks } from '../utils/mermaidMarkdown'
import { findNearestTextCursorBlockById } from './blockNoteCursorTarget'
interface BlockLike {
@@ -129,11 +129,11 @@ function getLineIndexFromRatio({ totalLines, ratio }: { totalLines: number; rati
}
function serializeBlock(editor: BlockNotePositionEditor, block: BlockLike): string {
- return compactMarkdown(serializeMathAwareBlocks(editor, restoreWikilinksInBlocks([block])))
+ return compactMarkdown(serializeMermaidAwareBlocks(editor, restoreWikilinksInBlocks([block])))
}
function serializeEditorBody(editor: BlockNotePositionEditor): string {
- return compactMarkdown(serializeMathAwareBlocks(editor, restoreWikilinksInBlocks(editor.document)))
+ return compactMarkdown(serializeMermaidAwareBlocks(editor, restoreWikilinksInBlocks(editor.document)))
}
function buildBlockLineRanges({
diff --git a/src/components/editorRawModeSync.mermaid.test.ts b/src/components/editorRawModeSync.mermaid.test.ts
new file mode 100644
index 00000000..98243eec
--- /dev/null
+++ b/src/components/editorRawModeSync.mermaid.test.ts
@@ -0,0 +1,31 @@
+import { describe, expect, it, vi } from 'vitest'
+import { MERMAID_BLOCK_TYPE } from '../utils/mermaidMarkdown'
+import { serializeEditorDocumentToMarkdown } from './editorRawModeSync'
+
+describe('editorRawModeSync Mermaid serialization', () => {
+ it('keeps the original fenced Mermaid source when rich content enters raw mode', () => {
+ const source = [
+ '~~~mermaid',
+ 'flowchart LR',
+ ' A["Draft"] --> B["Saved"]',
+ '~~~',
+ ].join('\n')
+ const editor = {
+ document: [{
+ id: 'diagram-1',
+ type: MERMAID_BLOCK_TYPE,
+ props: {
+ source,
+ diagram: 'flowchart LR\n A["Draft"] --> B["Saved"]\n',
+ },
+ children: [],
+ }],
+ blocksToMarkdownLossy: vi.fn(),
+ }
+
+ expect(serializeEditorDocumentToMarkdown(
+ editor as never,
+ '---\ntitle: Flow\n---\n\n# Flow\n',
+ )).toBe(`---\ntitle: Flow\n---\n${source}\n`)
+ })
+})
diff --git a/src/components/editorRawModeSync.ts b/src/components/editorRawModeSync.ts
index 45c9a7ea..eee80ce9 100644
--- a/src/components/editorRawModeSync.ts
+++ b/src/components/editorRawModeSync.ts
@@ -2,7 +2,7 @@ import type { useCreateBlockNote } from '@blocknote/react'
import type { VaultEntry } from '../types'
import { splitFrontmatter, restoreWikilinksInBlocks } from '../utils/wikilinks'
import { compactMarkdown } from '../utils/compact-markdown'
-import { serializeMathAwareBlocks } from '../utils/mathMarkdown'
+import { serializeMermaidAwareBlocks } from '../utils/mermaidMarkdown'
import { portableImageUrls } from '../utils/vaultImages'
interface Tab {
@@ -30,7 +30,7 @@ export function serializeEditorDocumentToMarkdown(
): string {
const blocks = editor.document
const restored = restoreWikilinksInBlocks(blocks)
- const rawBodyMarkdown = compactMarkdown(serializeMathAwareBlocks(editor, restored))
+ const rawBodyMarkdown = compactMarkdown(serializeMermaidAwareBlocks(editor, restored))
const bodyMarkdown = vaultPath ? portableImageUrls(rawBodyMarkdown, vaultPath) : rawBodyMarkdown
const [frontmatter] = splitFrontmatter(tabContent)
return `${frontmatter}${bodyMarkdown}`
diff --git a/src/components/editorSchema.tsx b/src/components/editorSchema.tsx
index 93976b24..d0131fb6 100644
--- a/src/components/editorSchema.tsx
+++ b/src/components/editorSchema.tsx
@@ -5,8 +5,10 @@ import { createReactBlockSpec, createReactInlineContentSpec } from '@blocknote/r
import { resolveWikilinkColor as resolveColor } from '../utils/wikilinkColors'
import { resolveEntry } from '../utils/wikilink'
import { MATH_BLOCK_TYPE, MATH_INLINE_TYPE, renderMathToHtml } from '../utils/mathMarkdown'
+import { MERMAID_BLOCK_TYPE } from '../utils/mermaidMarkdown'
import type { VaultEntry } from '../types'
import { NoteTitleIcon } from './NoteTitleIcon'
+import { MermaidDiagram } from './MermaidDiagram'
// Module-level cache so the WikiLink renderer (defined outside React) can access entries
export const _wikilinkEntriesRef: { current: VaultEntry[] } = { current: [] }
@@ -104,11 +106,31 @@ const MathBlock = createReactBlockSpec(
},
)
+const MermaidBlock = createReactBlockSpec(
+ {
+ type: MERMAID_BLOCK_TYPE,
+ propSchema: {
+ source: { default: '' },
+ diagram: { default: '' },
+ },
+ content: 'none',
+ },
+ {
+ render: (props) => (
+
+ ),
+ },
+)
+
const codeBlock = createCodeBlockSpec({
...codeBlockOptions,
defaultLanguage: 'text',
})
const mathBlock = MathBlock()
+const mermaidBlock = MermaidBlock()
export const schema = BlockNoteSchema.create({
inlineContentSpecs: {
@@ -120,5 +142,6 @@ export const schema = BlockNoteSchema.create({
blockSpecs: {
codeBlock,
mathBlock,
+ mermaidBlock,
},
})
diff --git a/src/hooks/useEditorTabSwap.ts b/src/hooks/useEditorTabSwap.ts
index e62dfeaf..674ede76 100644
--- a/src/hooks/useEditorTabSwap.ts
+++ b/src/hooks/useEditorTabSwap.ts
@@ -3,7 +3,8 @@ import type { useCreateBlockNote } from '@blocknote/react'
import type { VaultEntry } from '../types'
import { splitFrontmatter, preProcessWikilinks, injectWikilinks, restoreWikilinksInBlocks } from '../utils/wikilinks'
import { compactMarkdown } from '../utils/compact-markdown'
-import { injectMathInBlocks, preProcessMathMarkdown, serializeMathAwareBlocks } from '../utils/mathMarkdown'
+import { injectMathInBlocks, preProcessMathMarkdown } from '../utils/mathMarkdown'
+import { injectMermaidInBlocks, preProcessMermaidMarkdown, serializeMermaidAwareBlocks } from '../utils/mermaidMarkdown'
import { failNoteOpenTrace, finishNoteOpenTrace } from '../utils/noteOpenPerformance'
import { resolveImageUrls, portableImageUrls } from '../utils/vaultImages'
import {
@@ -134,6 +135,19 @@ async function parseMarkdownBlocks(
return result as EditorBlocks
}
+function preProcessEditorMarkdown(markdown: string, vaultPath?: string): string {
+ const withMermaid = preProcessMermaidMarkdown({ markdown })
+ const withImages = vaultPath ? resolveImageUrls(withMermaid, vaultPath) : withMermaid
+ const withWikilinks = preProcessWikilinks(withImages)
+ return preProcessMathMarkdown({ markdown: withWikilinks })
+}
+
+function injectEditorMarkdownBlocks(blocks: EditorBlocks): EditorBlocks {
+ const withWikilinks = injectWikilinks(blocks)
+ const withMath = injectMathInBlocks(withWikilinks)
+ return injectMermaidInBlocks(withMath) as EditorBlocks
+}
+
async function resolveBlocksForTarget(
options: {
editor: ReturnType
@@ -148,8 +162,7 @@ async function resolveBlocksForTarget(
if (cached?.sourceContent === content) return cached
const body = extractEditorBody(content)
- const withImages = vaultPath ? resolveImageUrls(body, vaultPath) : body
- const preprocessed = preProcessMathMarkdown({ markdown: preProcessWikilinks(withImages) })
+ const preprocessed = preProcessEditorMarkdown(body, vaultPath)
const fastPathBlocks = buildFastPathBlocks({ preprocessed })
if (fastPathBlocks) {
const nextState = { blocks: fastPathBlocks, scrollTop: 0, sourceContent: content }
@@ -158,9 +171,7 @@ async function resolveBlocksForTarget(
}
const parsed = normalizeParsedImageBlocks(await parseMarkdownBlocks(editor, preprocessed)) as EditorBlocks
- const withWikilinks = injectWikilinks(parsed)
- const withMath = injectMathInBlocks(withWikilinks)
- const nextState = { blocks: withMath, scrollTop: 0, sourceContent: content }
+ const nextState = { blocks: injectEditorMarkdownBlocks(parsed), scrollTop: 0, sourceContent: content }
cacheEditorState(cache, targetPath, nextState)
return nextState
}
@@ -247,13 +258,10 @@ async function resolveEmptyHeadingHtml(
if (remainder === null) return null
if (!remainder.trim()) return ''
- const withImages = vaultPath ? resolveImageUrls(remainder, vaultPath) : remainder
const parsed = normalizeParsedImageBlocks(
- await parseMarkdownBlocks(editor, preProcessMathMarkdown({ markdown: preProcessWikilinks(withImages) })),
+ await parseMarkdownBlocks(editor, preProcessEditorMarkdown(remainder, vaultPath)),
) as EditorBlocks
- const withWikilinks = injectWikilinks(parsed)
- const withMath = injectMathInBlocks(withWikilinks)
- return `${editor.blocksToHTMLLossy(withMath as typeof parsed)}`
+ return `${editor.blocksToHTMLLossy(injectEditorMarkdownBlocks(parsed) as typeof parsed)}`
}
function findActiveTab(options: {
@@ -268,7 +276,7 @@ function findActiveTab(options: {
function serializeEditorBody(editor: ReturnType): string {
const restored = restoreWikilinksInBlocks(editor.document)
- return compactMarkdown(serializeMathAwareBlocks(editor, restored))
+ return compactMarkdown(serializeMermaidAwareBlocks(editor, restored))
}
function normalizeTabBody(options: { content: string }): string {
diff --git a/src/utils/compact-markdown.ts b/src/utils/compact-markdown.ts
index a3b1c90d..dd3f2569 100644
--- a/src/utils/compact-markdown.ts
+++ b/src/utils/compact-markdown.ts
@@ -76,7 +76,8 @@ function processMarkdownLine(
}
function isFenceDelimiter({ line }: MarkdownLineValue): boolean {
- return line.trimStart().startsWith('```')
+ const trimmed = line.trimStart()
+ return trimmed.startsWith('```') || trimmed.startsWith('~~~')
}
function normalizeMarkdownLine({ line }: MarkdownLineValue): string {
diff --git a/src/utils/mermaidMarkdown.test.ts b/src/utils/mermaidMarkdown.test.ts
new file mode 100644
index 00000000..81b88d08
--- /dev/null
+++ b/src/utils/mermaidMarkdown.test.ts
@@ -0,0 +1,84 @@
+import { describe, expect, it, vi } from 'vitest'
+import {
+ MERMAID_BLOCK_TYPE,
+ injectMermaidInBlocks,
+ preProcessMermaidMarkdown,
+ serializeMermaidAwareBlocks,
+} from './mermaidMarkdown'
+
+describe('mermaid markdown round-trip', () => {
+ it('injects fenced Mermaid source into dedicated diagram blocks', () => {
+ const markdown = [
+ '```mermaid',
+ 'flowchart LR',
+ ' A --> B',
+ '```',
+ ].join('\n')
+ const preprocessed = preProcessMermaidMarkdown({ markdown })
+ const blocks = [{
+ type: 'paragraph',
+ content: [{ type: 'text', text: preprocessed, styles: {} }],
+ children: [],
+ }]
+
+ const [block] = injectMermaidInBlocks(blocks) as Array<{
+ type: string
+ props: { source: string; diagram: string }
+ }>
+
+ expect(block.type).toBe(MERMAID_BLOCK_TYPE)
+ expect(block.props.source).toBe(markdown)
+ expect(block.props.diagram).toBe('flowchart LR\n A --> B\n')
+ })
+
+ it('preserves multiple Mermaid blocks independently when serializing', () => {
+ const editor = {
+ blocksToMarkdownLossy: vi.fn((blocks: unknown[]) => {
+ return (blocks as Array<{ content?: Array<{ text?: string }> }>)
+ .map((block) => block.content?.map((item) => item.text ?? '').join('') ?? '')
+ .join('\n\n')
+ }),
+ }
+ const firstSource = '```mermaid\nflowchart TD\nA --> B\n```'
+ const secondSource = '~~~mermaid\nsequenceDiagram\nAlice->>Bob: Hi\n~~~'
+ const blocks = [
+ { type: 'paragraph', content: [{ type: 'text', text: 'Intro' }], children: [] },
+ { type: MERMAID_BLOCK_TYPE, props: { source: firstSource, diagram: 'flowchart TD\nA --> B\n' }, children: [] },
+ { type: 'paragraph', content: [{ type: 'text', text: 'Between' }], children: [] },
+ { type: MERMAID_BLOCK_TYPE, props: { source: secondSource, diagram: 'sequenceDiagram\nAlice->>Bob: Hi\n' }, children: [] },
+ ]
+
+ expect(serializeMermaidAwareBlocks(editor, blocks)).toBe([
+ 'Intro',
+ firstSource,
+ 'Between',
+ secondSource,
+ ].join('\n\n'))
+ })
+
+ it('leaves non-Mermaid and unclosed fences as normal Markdown', () => {
+ const markdown = [
+ '```ts',
+ 'const graph = "mermaid"',
+ '```',
+ '',
+ '```mermaid',
+ 'flowchart LR',
+ ].join('\n')
+
+ expect(preProcessMermaidMarkdown({ markdown })).toBe(markdown)
+ })
+
+ it('serializes fallback source for Mermaid blocks created without original fence text', () => {
+ const editor = { blocksToMarkdownLossy: vi.fn(() => '') }
+ const blocks = [{
+ type: MERMAID_BLOCK_TYPE,
+ props: { source: '', diagram: 'flowchart LR\nA --> B' },
+ children: [],
+ }]
+
+ expect(serializeMermaidAwareBlocks(editor, blocks)).toBe(
+ '```mermaid\nflowchart LR\nA --> B\n```',
+ )
+ })
+})
diff --git a/src/utils/mermaidMarkdown.ts b/src/utils/mermaidMarkdown.ts
new file mode 100644
index 00000000..4e13d810
--- /dev/null
+++ b/src/utils/mermaidMarkdown.ts
@@ -0,0 +1,241 @@
+import { serializeMathAwareBlocks } from './mathMarkdown'
+
+export const MERMAID_BLOCK_TYPE = 'mermaidBlock'
+
+const TOKEN_PREFIX = '@@TOLARIA_MERMAID_BLOCK:'
+const TOKEN_SUFFIX = '@@'
+
+interface InlineItem {
+ type: string
+ text?: string
+ props?: Record
+ content?: unknown
+ [key: string]: unknown
+}
+
+interface BlockLike {
+ type?: string
+ content?: InlineItem[]
+ props?: Record
+ children?: BlockLike[]
+ [key: string]: unknown
+}
+
+interface MarkdownSerializer {
+ blocksToMarkdownLossy: (blocks: unknown[]) => string
+}
+
+interface MermaidPayload {
+ source: string
+ diagram: string
+}
+
+interface MermaidFenceStart {
+ character: '`' | '~'
+ length: number
+}
+
+interface MarkdownLine {
+ line: string
+}
+
+interface EncodedPayload {
+ encoded: string
+}
+
+interface TokenText {
+ text: string
+}
+
+interface FenceSearch {
+ lines: string[]
+ start: number
+ opening: MermaidFenceStart
+}
+
+interface FenceRange {
+ lines: string[]
+ start: number
+ end: number
+}
+
+interface DiagramSource {
+ diagram: string
+}
+
+function lineEnding({ line }: MarkdownLine): string {
+ if (line.endsWith('\r\n')) return '\r\n'
+ return line.endsWith('\n') ? '\n' : ''
+}
+
+function lineText({ line }: MarkdownLine): string {
+ const ending = lineEnding({ line })
+ return ending ? line.slice(0, -ending.length) : line
+}
+
+function splitMarkdownLines(markdown: string): string[] {
+ const lines = markdown.match(/[^\n]*(?:\n|$)/g) ?? []
+ return lines.filter((line, index) => line !== '' || index < lines.length - 1)
+}
+
+function encodePayload(payload: MermaidPayload): string {
+ return encodeURIComponent(JSON.stringify(payload))
+}
+
+function decodePayload({ encoded }: EncodedPayload): MermaidPayload | null {
+ try {
+ const payload = JSON.parse(decodeURIComponent(encoded)) as Partial
+ if (typeof payload.source !== 'string') return null
+ if (typeof payload.diagram !== 'string') return null
+ return { source: payload.source, diagram: payload.diagram }
+ } catch {
+ return null
+ }
+}
+
+function mermaidToken(payload: MermaidPayload): string {
+ return `${TOKEN_PREFIX}${encodePayload(payload)}${TOKEN_SUFFIX}`
+}
+
+function readMermaidToken({ text }: TokenText): MermaidPayload | null {
+ const trimmed = text.trim()
+ if (!trimmed.startsWith(TOKEN_PREFIX) || !trimmed.endsWith(TOKEN_SUFFIX)) return null
+ return decodePayload({ encoded: trimmed.slice(TOKEN_PREFIX.length, -TOKEN_SUFFIX.length) })
+}
+
+function readMermaidFenceStart({ line }: MarkdownLine): MermaidFenceStart | null {
+ const match = /^( {0,3})(`{3,}|~{3,})[ \t]*(.*)$/.exec(line)
+ if (!match) return null
+
+ const fence = match[2]
+ const language = match[3].trim().split(/\s+/)[0]?.toLowerCase()
+ if (language !== 'mermaid') return null
+
+ return {
+ character: fence[0] as '`' | '~',
+ length: fence.length,
+ }
+}
+
+function isClosingFence({ line, opening }: MarkdownLine & { opening: MermaidFenceStart }): boolean {
+ const match = /^( {0,3})(`{3,}|~{3,})[ \t]*$/.exec(line)
+ if (!match) return false
+
+ const fence = match[2]
+ return fence[0] === opening.character && fence.length >= opening.length
+}
+
+function findClosingFence({ lines, start, opening }: FenceSearch): number {
+ for (let index = start + 1; index < lines.length; index++) {
+ if (isClosingFence({ line: lineText({ line: lines[index] }), opening })) return index
+ }
+
+ return -1
+}
+
+function buildPayload({ lines, start, end }: FenceRange): MermaidPayload {
+ return {
+ source: lines.slice(start, end + 1).join(''),
+ diagram: lines.slice(start + 1, end).join(''),
+ }
+}
+
+export function preProcessMermaidMarkdown({ markdown }: { markdown: string }): string {
+ const lines = splitMarkdownLines(markdown)
+ const result: string[] = []
+
+ for (let index = 0; index < lines.length; index++) {
+ const opening = readMermaidFenceStart({ line: lineText({ line: lines[index] }) })
+ if (!opening) {
+ result.push(lines[index])
+ continue
+ }
+
+ const closingIndex = findClosingFence({ lines, start: index, opening })
+ if (closingIndex === -1) {
+ result.push(lines[index])
+ continue
+ }
+
+ const payload = buildPayload({ lines, start: index, end: closingIndex })
+ result.push(`${mermaidToken(payload)}${lineEnding({ line: lines[closingIndex] })}`)
+ index = closingIndex
+ }
+
+ return result.join('')
+}
+
+function readMermaidPayload(content: InlineItem[] | undefined): MermaidPayload | null {
+ const onlyItem = content?.length === 1 ? content[0] : null
+ if (onlyItem?.type !== 'text' || typeof onlyItem.text !== 'string') return null
+ return readMermaidToken({ text: onlyItem.text })
+}
+
+function buildMermaidBlock({ block, payload }: { block: BlockLike; payload: MermaidPayload }): BlockLike {
+ return {
+ ...block,
+ type: MERMAID_BLOCK_TYPE,
+ props: {
+ ...(block.props ?? {}),
+ source: payload.source,
+ diagram: payload.diagram,
+ },
+ content: undefined,
+ children: [],
+ }
+}
+
+function injectMermaidInBlock(block: BlockLike): BlockLike {
+ const payload = readMermaidPayload(block.content)
+ if (payload) return buildMermaidBlock({ block, payload })
+
+ const children = Array.isArray(block.children) ? block.children.map(injectMermaidInBlock) : block.children
+ return { ...block, children }
+}
+
+function isMermaidBlock(block: BlockLike): boolean {
+ return block.type === MERMAID_BLOCK_TYPE
+ && typeof block.props?.source === 'string'
+ && typeof block.props?.diagram === 'string'
+}
+
+function fallbackMermaidSource({ diagram }: DiagramSource): string {
+ const body = diagram.endsWith('\n') ? diagram : `${diagram}\n`
+ return `\`\`\`mermaid\n${body}\`\`\``
+}
+
+function mermaidMarkdown(block: BlockLike): string {
+ const source = block.props?.source
+ if (source) return source
+
+ return fallbackMermaidSource({ diagram: block.props?.diagram ?? '' })
+}
+
+export function injectMermaidInBlocks(blocks: unknown[]): unknown[] {
+ return (blocks as BlockLike[]).map(injectMermaidInBlock)
+}
+
+export function serializeMermaidAwareBlocks(editor: MarkdownSerializer, blocks: unknown[]): string {
+ const chunks: string[] = []
+ let pending: unknown[] = []
+
+ const flushPending = () => {
+ if (pending.length === 0) return
+
+ const markdown = serializeMathAwareBlocks(editor, pending).trimEnd()
+ if (markdown) chunks.push(markdown)
+ pending = []
+ }
+
+ for (const block of blocks as BlockLike[]) {
+ if (isMermaidBlock(block)) {
+ flushPending()
+ chunks.push(mermaidMarkdown(block))
+ } else {
+ pending.push(block)
+ }
+ }
+
+ flushPending()
+ return chunks.join('\n\n')
+}
diff --git a/tests/smoke/mermaid-diagram-notes.spec.ts b/tests/smoke/mermaid-diagram-notes.spec.ts
new file mode 100644
index 00000000..97068278
--- /dev/null
+++ b/tests/smoke/mermaid-diagram-notes.spec.ts
@@ -0,0 +1,150 @@
+import { expect, test, type Page } from '@playwright/test'
+import fs from 'fs'
+import path from 'path'
+import { createFixtureVaultCopy, openFixtureVault, removeFixtureVaultCopy } from '../helpers/fixtureVault'
+import { executeCommand, openCommandPalette } from './helpers'
+
+let tempVaultDir: string
+
+const FIRST_DIAGRAM = [
+ '```mermaid',
+ 'flowchart LR',
+ ' A[Draft] --> B[Saved]',
+ '```',
+].join('\n')
+const UPDATED_FIRST_DIAGRAM = FIRST_DIAGRAM.replace('B[Saved]', 'C[Published]')
+const SECOND_DIAGRAM = [
+ '```mermaid',
+ 'sequenceDiagram',
+ ' Alice->>Bob: Hello',
+ '```',
+].join('\n')
+const INVALID_DIAGRAM = [
+ '```mermaid',
+ 'not a diagram',
+ '```',
+].join('\n')
+
+test.beforeEach(async ({ page }, testInfo) => {
+ testInfo.setTimeout(90_000)
+ tempVaultDir = createFixtureVaultCopy()
+ await openFixtureVault(page, tempVaultDir)
+})
+
+test.afterEach(async () => {
+ removeFixtureVaultCopy(tempVaultDir)
+})
+
+async function openNote(page: Page, title: string): Promise {
+ await page.locator('[data-testid="note-list-container"]').getByText(title, { exact: true }).click()
+ await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5_000 })
+}
+
+async function toggleRawMode(page: Page, visibleSelector: '.bn-editor' | '.cm-content'): Promise {
+ await openCommandPalette(page)
+ await executeCommand(page, 'Toggle Raw')
+ await expect(page.locator(visibleSelector)).toBeVisible({ timeout: 5_000 })
+}
+
+async function getRawEditorContent(page: Page): Promise {
+ return page.evaluate(() => {
+ type CodeMirrorHost = Element & {
+ cmTile?: {
+ view?: {
+ state: {
+ doc: {
+ toString(): string
+ }
+ }
+ }
+ }
+ }
+
+ const host = document.querySelector('.cm-content') as CodeMirrorHost | null
+ return host?.cmTile?.view?.state.doc.toString() ?? host?.textContent ?? ''
+ })
+}
+
+async function setRawEditorContent(page: Page, content: string): Promise {
+ await page.evaluate((nextContent) => {
+ type CodeMirrorHost = Element & {
+ cmTile?: {
+ view?: {
+ state: {
+ doc: {
+ length: number
+ }
+ }
+ dispatch(update: {
+ changes: {
+ from: number
+ to: number
+ insert: string
+ }
+ }): void
+ }
+ }
+ }
+
+ const host = document.querySelector('.cm-content') as CodeMirrorHost | null
+ const view = host?.cmTile?.view
+ if (!view) return
+
+ view.dispatch({
+ changes: { from: 0, to: view.state.doc.length, insert: nextContent },
+ })
+ }, content)
+}
+
+async function expectRenderedDiagramCount(page: Page, count: number): Promise {
+ await expect(page.locator('[data-testid="mermaid-diagram-viewport"] svg')).toHaveCount(count, { timeout: 15_000 })
+}
+
+function readNoteBFile(): string {
+ return fs.readFileSync(path.join(tempVaultDir, 'note', 'note-b.md'), 'utf8')
+}
+
+test('Mermaid diagrams render, fall back, and round-trip through raw mode', async ({ page }) => {
+ await openNote(page, 'Note B')
+ await toggleRawMode(page, '.cm-content')
+
+ const originalContent = await getRawEditorContent(page)
+ const nextContent = `${originalContent.trimEnd()}
+
+${FIRST_DIAGRAM}
+
+${INVALID_DIAGRAM}
+
+${SECOND_DIAGRAM}
+`
+
+ await setRawEditorContent(page, nextContent)
+ await expect.poll(readNoteBFile).toContain(FIRST_DIAGRAM)
+
+ await toggleRawMode(page, '.bn-editor')
+ await expectRenderedDiagramCount(page, 2)
+ await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toHaveCount(1)
+ await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toContainText('not a diagram')
+
+ await toggleRawMode(page, '.cm-content')
+ const rawAfterRichMode = await getRawEditorContent(page)
+ expect(rawAfterRichMode).toContain(FIRST_DIAGRAM)
+ expect(rawAfterRichMode).toContain(INVALID_DIAGRAM)
+ expect(rawAfterRichMode).toContain(SECOND_DIAGRAM)
+
+ await setRawEditorContent(page, rawAfterRichMode.replace(FIRST_DIAGRAM, UPDATED_FIRST_DIAGRAM))
+ await expect.poll(readNoteBFile).toContain(UPDATED_FIRST_DIAGRAM)
+
+ await toggleRawMode(page, '.bn-editor')
+ await expectRenderedDiagramCount(page, 2)
+ await expect(page.locator('[data-testid="mermaid-diagram-viewport"]').first()).toContainText('Published')
+
+ await openNote(page, 'Note C')
+ await openNote(page, 'Note B')
+ await toggleRawMode(page, '.cm-content')
+
+ const reopenedRaw = await getRawEditorContent(page)
+ expect(reopenedRaw).toContain(UPDATED_FIRST_DIAGRAM)
+ expect(reopenedRaw).toContain(INVALID_DIAGRAM)
+ expect(reopenedRaw).toContain(SECOND_DIAGRAM)
+})