From 7d0947cc3f96631377b0730cc1f1244998596c1a Mon Sep 17 00:00:00 2001
From: stevenjj33 <75509501+stevenjj33@users.noreply.github.com>
Date: Tue, 13 Oct 2026 16:54:36 +0800
Subject: [PATCH 1/3] feat(webui): render Mermaid fences in the transcript
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes the acceptance table''s "markdown-图表(mmd)" row: the table said
"通过" while the source had zero Mermaid support, so a model-drawn
flowchart or sequence diagram reached the transcript as plain text.
The fence branch sits in `markdown.tsx` between the math and generic
code branches and fires only for the `mermaid`/`mmd` language words —
`codeFenceLanguage` already strips tag attributes, so a fence tagged
`mermaid title="a.mmd"` is recognised, and the match is
case-insensitive. Every other fence keeps the untouched path, which
C-1 and C-2 pin byte for byte.
Rendering lives in the new `webui-mermaid.tsx`. The library loads
through a dynamic `import()` on the first fence that needs it, and
invalid syntax, an empty body, a thrown render and a render past the
timeout all fall back to the plain fence — the same failure habit as
katex''s `
` fallback, minus the gutter a Mermaid fence never had.
The component is split into a pure block and a thin effect wrapper so
the Node-environment suite can drive each state without a DOM, the
pattern `ReviewSettingsPage.tsx` already uses.
Build: the client bundle is now split (`splitting: true`, chunks under
`assets/`). Without splitting esbuild inlines every dynamic import
into the entry — mermaid''s source took `client.js` from 4.11 MB to
15.35 MB. Measured on the metafile, the initial payload is unchanged
at 4.11 MB and the ~11 MB of diagram chunks download only when a
fence appears. `test/webui-browser/server.mjs` serves the `assets/`
prefix, matching the loopback service''s static resolver.
`webui-dependency-rules.mjs` classifies the new file as `bindings`,
its siblings'' layer, so the direction gate stays green.
Verification: `markdown-mermaid.test.tsx` (18 cases) and the C-1/C-2
suites pass, typecheck:client and typecheck:test report 0 errors, and
the browser suite renders a real `` — removing the branch turns
that case red, which is the reverse check the task book asks for. On
Windows, 13 pre-existing suite failures (path-with-space
normalisation, symlink privilege, websocket shutdown timing) fail
identically on the base commit.
---
packages/webui/package.json | 1 +
.../client/components/transcript/markdown.tsx | 9 +
.../components/transcript/webui-mermaid.tsx | 201 ++++
packages/webui/src/client/styles/shell.css | 25 +
.../webui/test/unit/markdown-mermaid.test.tsx | 240 +++++
pnpm-lock.yaml | 923 +++++++++++++++++-
release/public-source.json | 2 +
scripts/build-webui.mjs | 10 +
scripts/package-webui-npm.mjs | 4 +
test/vitest-suites.json | 1 +
test/webui-browser/server.mjs | 39 +-
test/webui-browser/transcript.spec.mjs | 34 +-
12 files changed, 1477 insertions(+), 12 deletions(-)
create mode 100644 packages/webui/src/client/components/transcript/webui-mermaid.tsx
create mode 100644 packages/webui/test/unit/markdown-mermaid.test.tsx
diff --git a/packages/webui/package.json b/packages/webui/package.json
index 6debf7c59..b2f6f0c3d 100644
--- a/packages/webui/package.json
+++ b/packages/webui/package.json
@@ -34,6 +34,7 @@
"katex": "0.18.7",
"lottie-web": "^5.13.0",
"marked": "18.0.12",
+ "mermaid": "12.1.0",
"node-pty": "1.1.0",
"react": "18.3.1",
"react-dom": "18.3.1",
diff --git a/packages/webui/src/client/components/transcript/markdown.tsx b/packages/webui/src/client/components/transcript/markdown.tsx
index 0ffbefe2d..c396913c9 100644
--- a/packages/webui/src/client/components/transcript/markdown.tsx
+++ b/packages/webui/src/client/components/transcript/markdown.tsx
@@ -24,6 +24,7 @@ import {
type ReactNode,
} from "react";
import { parseWebuiMessageFileReference } from "../../projection/transcript/message-file-reference.js";
+import { isWebuiMermaidFence, WebuiMermaid } from "./webui-mermaid.js";
// The same `highlight.js/lib/core` registration `components/workspace/WorkspacePanels.tsx`
// performs for the file viewer — the full `highlight.js` bundle is never
@@ -259,6 +260,14 @@ function blocks(tokens: readonly Token[] | undefined, onOpenFile?: (reference: N
return {token.text} ;
}
}
+ // Mermaid fences branch off before the generic code path: the diagram
+ // renders to SVG through `WebuiMermaid`, and every failure there lands
+ // back on the plain fence. `codeFenceLanguage` strips the tag's trailing
+ // attributes, so `mermaid title="a.mmd"` is recognised the same as a
+ // bare `mermaid` tag. No other fence is affected by this branch.
+ if (token.type === "code" && isWebuiMermaidFence(codeFenceLanguage(token.lang))) {
+ return ;
+ }
if (token.type === "code") {
const language = codeFenceLanguage(token.lang);
const highlighted = highlightCodeFence(token.text, language);
diff --git a/packages/webui/src/client/components/transcript/webui-mermaid.tsx b/packages/webui/src/client/components/transcript/webui-mermaid.tsx
new file mode 100644
index 000000000..794faaf75
--- /dev/null
+++ b/packages/webui/src/client/components/transcript/webui-mermaid.tsx
@@ -0,0 +1,201 @@
+// webui-mermaid.tsx — Mermaid diagram rendering for the transcript.
+//
+// SPEC-C's acceptance table carries a "markdown-图表(mmd) 通过" row, but the
+// source had zero Mermaid support: a ```mermaid fence fell through to the
+// plain code path, so a model-drawn flowchart or sequence diagram arrived as
+// an unreadable blob of text. This file closes that row the same way the
+// renderer's other two languages already work — katex owns the math fences,
+// highlight.js owns the registered code languages, and Mermaid owns its own
+// fence word.
+//
+// Three deliberate choices, all driven by the acceptance contract:
+//
+// 1. `mermaid` is loaded with a dynamic `import()` and never referenced
+// statically. The package is large, and the transcript shows a Mermaid
+// fence only occasionally, so it must not sit in the main chunk. The
+// import happens on the first fence that actually needs it.
+// 2. Rendering is imperative (`mermaid.render`), not declarative
+// (`` + `mermaid.run`). `run()` swaps the DOM
+// itself and renders its own error box on a bad diagram; `render()`
+// rejects, which is what lets every failure — invalid syntax, an empty
+// body, a thrown render, a hung render — fall back to the plain fence
+// this component would have produced before Mermaid existed.
+// 3. The component is split into a pure presentational block
+// (`WebuiMermaidBlock`) and a thin effectful wrapper (`WebuiMermaid`).
+// The webui suite runs in Node with `renderToStaticMarkup`, which never
+// runs effects, so the block carries each state as a plain prop and the
+// wrapper only wires the async render into it. The same split
+// `components/workspace/ReviewSettingsPage.tsx` uses.
+//
+// Security: the SVG is injected with `dangerouslySetInnerHTML`, as katex and
+// highlight.js output already are in `markdown.tsx`. Mermaid is initialised
+// with `securityLevel: "strict"`, which is also its default, so the markup it
+// returns carries no external references.
+
+import { useEffect, useState, type ReactElement } from "react";
+
+/** The fence language words that select the Mermaid branch, compared lower-cased. */
+const MERMAID_FENCE_LANGUAGES = new Set(["mermaid", "mmd"]);
+
+/**
+ * How long a single `mermaid.render` may take before the diagram is abandoned
+ * for the plain-fence fallback. Generous on purpose: the browser suite renders
+ * the real library on CI hardware, and a tight budget turns a slow machine
+ * into a red build rather than a diagram.
+ */
+export const WEBUI_MERMAID_RENDER_TIMEOUT_MS = 10_000;
+
+/**
+ * Whether a fence tag names the Mermaid diagram language. The caller passes
+ * the fence's first language word (see `codeFenceLanguage` in `markdown.tsx`,
+ * which already strips trailing attributes such as `title="a.mmd"`), so a
+ * ```mermaid title="a.mmd"``` fence is recognised the same as a bare one.
+ * Comparison is case-insensitive because fence tags are conventionally lower
+ * case but nothing enforces it.
+ */
+export function isWebuiMermaidFence(language: string | undefined): boolean {
+ return language !== undefined && MERMAID_FENCE_LANGUAGES.has(language.toLowerCase());
+}
+
+/** Mermaid is configured once per document; `initialize` is not cheap to repeat. */
+let mermaidConfigured = false;
+
+/** Mermaid hands every render a DOM id; a monotonic counter keeps them unique. */
+let diagramSequence = 0;
+
+type MermaidModule = typeof import("mermaid");
+
+async function loadMermaid(): Promise {
+ const module = await import("mermaid");
+ if (!mermaidConfigured) {
+ // `startOnLoad: false` matters: the default scans the document for
+ // `.mermaid` elements on import, which would race the first explicit
+ // render. Nothing in this component uses that class, so the scan is
+ // disabled outright.
+ module.default.initialize({ startOnLoad: false, securityLevel: "strict" });
+ mermaidConfigured = true;
+ }
+ return module;
+}
+
+export interface WebuiMermaidRenderOptions {
+ /**
+ * Override for {@link WEBUI_MERMAID_RENDER_TIMEOUT_MS}. Exists so a test can
+ * drive the timeout path with a real clock instead of faking one; production
+ * callers never pass it.
+ */
+ readonly timeoutMs?: number;
+}
+
+/**
+ * Render one Mermaid diagram body to an SVG string.
+ *
+ * Rejects — rather than returning a fallback — on every failure so the caller
+ * decides what the user sees: an empty body, invalid syntax, a thrown render
+ * and a render that outlives the timeout all end here. The empty-body check
+ * runs before the dynamic import so an empty fence never pulls the library
+ * down the wire.
+ */
+export async function renderWebuiMermaidSvg(text: string, options?: WebuiMermaidRenderOptions): Promise {
+ if (text.trim() === "") throw new Error("webui-mermaid: empty diagram body");
+ const mermaid = (await loadMermaid()).default;
+ diagramSequence += 1;
+ const diagramId = `webui-mermaid-${diagramSequence}`;
+ const timeoutMs = options?.timeoutMs ?? WEBUI_MERMAID_RENDER_TIMEOUT_MS;
+ let timer: ReturnType | undefined;
+ try {
+ const result = await Promise.race([
+ mermaid.render(diagramId, text),
+ new Promise((_resolve, reject) => {
+ timer = setTimeout(
+ () => reject(new Error(`webui-mermaid: render timed out after ${timeoutMs}ms`)),
+ timeoutMs,
+ );
+ }),
+ ]);
+ return result.svg;
+ } finally {
+ if (timer !== undefined) clearTimeout(timer);
+ }
+}
+
+/** What the fence currently shows. See {@link WebuiMermaidBlock}. */
+export type WebuiMermaidState =
+ | { readonly status: "pending" }
+ | { readonly status: "rendered"; readonly svg: string }
+ | { readonly status: "failed" };
+
+export interface WebuiMermaidBlockProps {
+ readonly state: WebuiMermaidState;
+ /** The fence body, verbatim. */
+ readonly text: string;
+ /** The fence tag as marked reported it, attributes included. */
+ readonly lang: string | undefined;
+}
+
+/**
+ * The fence's markup for each state, with no effect of its own:
+ *
+ * pending — the `webui-mermaid` container holding the raw diagram text. The
+ * container is present from the first paint, so a slow chunk load shows
+ * the source rather than an empty box, and the unit suite can assert the
+ * container without ever running an effect.
+ * rendered — the same container with Mermaid's SVG inside it.
+ * failed — the plain fence, byte for byte: a `webui-code-block` wrapper, a
+ * `pre`, a `code` carrying the original text, and no gutter, because a
+ * Mermaid fence never shows line numbers in any of its states. This is
+ * the markup the fence had before Mermaid existed, minus the gutter.
+ */
+export function WebuiMermaidBlock({ state, text, lang }: WebuiMermaidBlockProps): ReactElement {
+ if (state.status === "rendered") {
+ return (
+
+ );
+ }
+ if (state.status === "failed") {
+ return (
+
+ );
+ }
+ return (
+
+ );
+}
+
+export interface WebuiMermaidProps {
+ /** The fence body, verbatim. */
+ readonly text: string;
+ /** The fence tag as marked reported it, attributes included. */
+ readonly lang: string | undefined;
+}
+
+/**
+ * The effectful half: renders the diagram once per body and repaints the block
+ * with the outcome. `cancelled` guards the state updates so a fence that
+ * unmounts mid-render — a session switch, a trimmed transcript — writes
+ * nothing. The dependency is the body alone: a re-render with the same text
+ * must not re-run the library.
+ */
+export function WebuiMermaid({ text, lang }: WebuiMermaidProps): ReactElement {
+ const [state, setState] = useState({ status: "pending" });
+ useEffect(() => {
+ let cancelled = false;
+ renderWebuiMermaidSvg(text)
+ .then((svg) => {
+ if (!cancelled) setState({ status: "rendered", svg });
+ })
+ .catch(() => {
+ if (!cancelled) setState({ status: "failed" });
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [text]);
+ return ;
+}
diff --git a/packages/webui/src/client/styles/shell.css b/packages/webui/src/client/styles/shell.css
index 594d838ab..13765b381 100644
--- a/packages/webui/src/client/styles/shell.css
+++ b/packages/webui/src/client/styles/shell.css
@@ -3551,6 +3551,31 @@
.webui-code-block .hljs-title, .webui-code-block .hljs-title.function_, .webui-code-block .hljs-function, .webui-code-block .hljs-section { color: var(--text_label_accent_default); }
.webui-code-block .hljs-type, .webui-code-block .hljs-built_in, .webui-code-block .hljs-title.class_ { color: var(--text_label_danger_secondary_default); }
.webui-code-block .hljs-attr, .webui-code-block .hljs-variable, .webui-code-block .hljs-property { color: var(--text_default_secondary); }
+ /* Mermaid diagrams: the container the rendered SVG lands in, and the raw
+ diagram text before the render settles. The surface tokens mirror
+ `.webui-code-block` so a diagram and a fence read as one panel. The SVG
+ itself carries Mermaid's own inline styles (rendered under
+ `securityLevel: "strict"`, so no external references); only its scaling
+ is pinned here. The `pre` covers the pending state, which shows the
+ diagram source while the library chunk loads. */
+ .webui-mermaid {
+ background-color: var(--bg_grouped_tertiary);
+ border-radius: var(--radius_16);
+ overflow: hidden;
+ padding: var(--spacing_12) var(--spacing_16);
+ }
+ .webui-mermaid pre {
+ margin: 0;
+ max-height: 400px;
+ overflow: auto;
+ font-size: 13px;
+ line-height: 20px;
+ }
+ .webui-mermaid svg {
+ display: block;
+ max-width: 100%;
+ height: auto;
+ }
.webui-table-shell {
max-width: 100%;
overflow-x: auto;
diff --git a/packages/webui/test/unit/markdown-mermaid.test.tsx b/packages/webui/test/unit/markdown-mermaid.test.tsx
new file mode 100644
index 000000000..28d5b0e87
--- /dev/null
+++ b/packages/webui/test/unit/markdown-mermaid.test.tsx
@@ -0,0 +1,240 @@
+// Mermaid fences in the transcript renderer — the "markdown-图表(mmd)" row of
+// SPEC-C's acceptance table.
+//
+// The row said "通过" while the source had zero Mermaid support; this file
+// pins the behaviour that closes it. `markdown-codeblock-c1-highlight.test.tsx`
+// and `markdown-codeblock-c2-line-numbers.test.tsx` are the regression
+// guardrails for every other fence language — their exact-literal assertions
+// on the plain and highlighted paths are what proves a Mermaid fence did not
+// disturb them — so this file covers the Mermaid surface itself:
+//
+// 1. `mermaid` and `mmd` fences (with or without tag attributes, in any
+// case) take the Mermaid branch and produce a `webui-mermaid` container,
+// never a `webui-code-block`.
+// 2. An empty body, invalid syntax, a thrown render and a render that
+// outlives the timeout all reject out of `renderWebuiMermaidSvg`, and
+// the failed state renders the plain fence with the original text one
+// character unchanged.
+// 3. Other languages stay byte-identical, asserted here against the same
+// literal the pre-Mermaid renderer produced.
+//
+// On the environment: the webui suite runs in Node with no jsdom, and
+// `renderToStaticMarkup` renders once without running effects. So the async
+// pipeline is driven directly against a stubbed `mermaid` module, the three
+// states are rendered as plain props of `WebuiMermaidBlock`, and the effect
+// that wires them together in a browser is covered by
+// `test/webui-browser/transcript.spec.mjs` — the same split
+// `review-panel.test.tsx` documents for its component.
+
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { createElement } from "react";
+import { renderToStaticMarkup } from "react-dom/server";
+
+import { WebuiMarkdown } from "../../src/client/components/transcript/markdown.js";
+import {
+ isWebuiMermaidFence,
+ renderWebuiMermaidSvg,
+ WebuiMermaidBlock,
+ type WebuiMermaidState,
+} from "../../src/client/components/transcript/webui-mermaid.js";
+import mermaid from "mermaid";
+
+// The real library needs a browser; every test below drives the stub. The
+// default export is the documented surface (`initialize` + `render`).
+vi.mock("mermaid", () => ({
+ default: {
+ initialize: vi.fn(),
+ render: vi.fn(),
+ },
+}));
+
+const renderMock = vi.mocked(mermaid.render);
+
+function render(source: string): string {
+ return renderToStaticMarkup(createElement(WebuiMarkdown, { source }));
+}
+
+function renderBlock(state: WebuiMermaidState, text: string, lang?: string): string {
+ return renderToStaticMarkup(createElement(WebuiMermaidBlock, { state, text, lang }));
+}
+
+/** The `` element's inner HTML — the part a fallback must preserve. */
+function codeInnerHtml(markup: string): string {
+ const open = markup.indexOf("", open) + 1;
+ const end = markup.indexOf("", start);
+ return markup.slice(start, end);
+}
+
+function decodeEntities(value: string): string {
+ return value
+ .replace(/</gu, "<")
+ .replace(/>/gu, ">")
+ .replace(/"/gu, '"')
+ .replace(/'/gu, "'")
+ .replace(/&/gu, "&");
+}
+
+/** The document `WebuiMarkdown` produced for a plain fence before Mermaid existed. */
+function expectedPlainDocument(lang: string, body: string): string {
+ return (
+ ``
+ );
+}
+
+afterEach(() => {
+ renderMock.mockReset();
+});
+
+describe("which fences take the Mermaid branch", () => {
+ it("recognises mermaid and mmd in any letter case", () => {
+ expect(isWebuiMermaidFence("mermaid")).toBe(true);
+ expect(isWebuiMermaidFence("mmd")).toBe(true);
+ expect(isWebuiMermaidFence("Mermaid")).toBe(true);
+ expect(isWebuiMermaidFence("MMD")).toBe(true);
+ });
+
+ it("recognises a tag whose attributes were stripped to its first word", () => {
+ // marked puts everything after the backticks in `token.lang`, so
+ // ```mermaid title="a.mmd"``` arrives here as the bare language word.
+ expect(isWebuiMermaidFence("mermaid")).toBe(true);
+ });
+
+ it("leaves every other fence language alone", () => {
+ expect(isWebuiMermaidFence("ts")).toBe(false);
+ expect(isWebuiMermaidFence("math")).toBe(false);
+ expect(isWebuiMermaidFence("notalanguage")).toBe(false);
+ expect(isWebuiMermaidFence(undefined)).toBe(false);
+ });
+});
+
+describe("renderWebuiMermaidSvg", () => {
+ it("returns the library's SVG for a valid diagram", async () => {
+ renderMock.mockResolvedValue({ svg: ' ' } as never);
+ await expect(renderWebuiMermaidSvg("graph TD; A-->B;")).resolves.toContain(" {
+ await expect(renderWebuiMermaidSvg("")).rejects.toThrow(/empty/iu);
+ await expect(renderWebuiMermaidSvg(" \n \n")).rejects.toThrow(/empty/iu);
+ expect(renderMock).not.toHaveBeenCalled();
+ });
+
+ it("rejects invalid syntax", async () => {
+ renderMock.mockRejectedValue(new Error("Parse error"));
+ await expect(renderWebuiMermaidSvg("this is not a diagram @@")).rejects.toThrow(/Parse error/iu);
+ });
+
+ it("rejects a render that outlives the timeout", async () => {
+ // A real clock with an injected budget: faking timers here would put the
+ // rejection inside a timer-advance window, which Vitest reports as an
+ // unhandled rejection even though the assertion passes.
+ renderMock.mockImplementation(() => new Promise(() => {}) as never);
+ await expect(
+ renderWebuiMermaidSvg("graph TD; A-->B;", { timeoutMs: 20 }),
+ ).rejects.toThrow(/timed out after 20ms/iu);
+ });
+});
+
+describe("WebuiMermaidBlock", () => {
+ const body = "graph TD; A-->B;";
+
+ it("shows the container and the raw diagram while pending, with no code block", () => {
+ const markup = renderBlock({ status: "pending" }, body, "mermaid");
+ expect(markup).toContain('class="webui-mermaid"');
+ expect(markup).toContain('data-webui-mermaid-state="pending"');
+ expect(markup).not.toContain("webui-code-block");
+ // The pending state carries the diagram source, escaped like any text node.
+ expect(decodeEntities(markup.replace(/<[^>]*>/gu, ""))).toBe(body);
+ });
+
+ it("injects the library's SVG once rendered", () => {
+ const markup = renderBlock({ status: "rendered", svg: ' ' }, body, "mermaid");
+ expect(markup).toContain('class="webui-mermaid"');
+ expect(markup).toContain('data-webui-mermaid-state="rendered"');
+ expect(markup).toContain('');
+ });
+
+ it("falls back to the plain fence, one character unchanged, with no gutter", () => {
+ const markup = renderBlock({ status: "failed" }, body, "mermaid");
+ expect(markup).toContain('class="webui-code-block"');
+ expect(markup).not.toContain("webui-code-block--numbered");
+ expect(markup).not.toContain("webui-code-gutter");
+ expect(decodeEntities(codeInnerHtml(markup))).toBe(body);
+ });
+
+ it("keeps markup-bearing diagram text exact through the fallback", () => {
+ const tricky = "graph TD; A-->B; Note[\"a & b < c\"];";
+ const markup = renderBlock({ status: "failed" }, tricky, "mermaid");
+ // The raw substring must not survive unescaped anywhere in the markup.
+ expect(markup).not.toContain('a & b < c');
+ expect(decodeEntities(codeInnerHtml(markup))).toBe(tricky);
+ });
+
+ it("carries the fence tag, attributes included, as the code element's language", () => {
+ const markup = renderBlock({ status: "failed" }, body, 'mermaid title="a.mmd"');
+ expect(markup).toContain('data-language="mermaid title="a.mmd""');
+ });
+});
+
+describe("WebuiMarkdown renders a Mermaid fence", () => {
+ const body = "graph TD; A-->B;";
+
+ it("produces the webui-mermaid container and no code block", () => {
+ const markup = render(`\`\`\`mermaid\n${body}\n\`\`\``);
+ expect(markup).toContain('class="webui-mermaid"');
+ expect(markup).not.toContain("webui-code-block");
+ });
+
+ it("recognises the mmd alias and a tag carrying attributes", () => {
+ for (const tag of ["mmd", 'mermaid title="a.mmd"', "MERMAID"]) {
+ const markup = render(`\`\`\`${tag}\n${body}\n\`\`\``);
+ expect(markup, tag).toContain('class="webui-mermaid"');
+ expect(markup, tag).not.toContain("webui-code-block");
+ }
+ });
+
+ it("does not crash on an empty fence", () => {
+ let markup = "";
+ expect(() => {
+ markup = render("```mermaid\n```");
+ }).not.toThrow();
+ expect(markup).toContain('class="webui-mermaid"');
+ });
+
+ it("does not crash on a single-line fence", () => {
+ let markup = "";
+ expect(() => {
+ markup = render("```mermaid\ngraph TD;A-->B;\n```");
+ }).not.toThrow();
+ expect(markup).toContain('class="webui-mermaid"');
+ expect(decodeEntities(markup.replace(/<[^>]*>/gu, ""))).toContain("graph TD;A-->B;");
+ });
+});
+
+describe("every other fence language is untouched", () => {
+ it("keeps the plain path byte for byte", () => {
+ expect(render("```notalanguage\nplain body\n```")).toBe(expectedPlainDocument("notalanguage", "plain body"));
+ expect(render("```\nno lang\n```")).toBe(expectedPlainDocument("", "no lang"));
+ expect(render("```ts\n```")).toBe(expectedPlainDocument("ts", ""));
+ });
+
+ it("keeps the highlighted path's shape and text for registered languages", () => {
+ // The C-1 suite pins these literals against the pre-Mermaid renderer;
+ // this is the same contract restated so a Mermaid-side regression fails
+ // here too.
+ const cases: readonly [string, string][] = [
+ ["ts", "const answer = 42;"],
+ ["python", "print('hi')"],
+ ["bash", "echo hi"],
+ ];
+ for (const [lang, body] of cases) {
+ const markup = render(`\`\`\`${lang}\n${body}\n\`\`\``);
+ expect(markup, lang).toContain(``);
+ expect(markup, lang).not.toContain("webui-mermaid");
+ expect(decodeEntities(codeInnerHtml(markup).replace(/<[^>]*>/gu, "")), lang).toBe(body);
+ }
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a6b20ff12..31e100072 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -936,9 +936,6 @@ importers:
'@xterm/xterm':
specifier: 6.0.0
version: 6.0.0
- better-sqlite3:
- specifier: 12.11.1
- version: 12.11.1
highlight.js:
specifier: 10.7.3
version: 10.7.3
@@ -951,6 +948,9 @@ importers:
marked:
specifier: 18.0.12
version: 18.0.12
+ mermaid:
+ specifier: 12.1.0
+ version: 12.1.0
node-pty:
specifier: 1.1.0
version: 1.1.0
@@ -996,10 +996,10 @@ importers:
version: 5.9.3
vite:
specifier: 7.3.6
- version: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)
+ version: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1)
vitest:
specifier: 4.1.11
- version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
+ version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1))
third_party/pi-mono/packages/agent:
dependencies:
@@ -1213,6 +1213,9 @@ packages:
resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
engines: {node: '>=10'}
+ '@antfu/install-pkg@2.1.0':
+ resolution: {integrity: sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==}
+
'@anthropic-ai/sdk@0.91.1':
resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==}
hasBin: true
@@ -1344,6 +1347,24 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
+ '@braintree/sanitize-url@7.1.2':
+ resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
+
+ '@chevrotain/cst-dts-gen@13.2.0':
+ resolution: {integrity: sha512-bA9dvWlhAUcHkNwU+8w9yiUxJPlj/Lm3VqS7MD+NbIR/7LmpNLuK/AWNFdCo/T3SK5ND0/UHf3IJvl/Jn/i6yg==}
+
+ '@chevrotain/gast@13.2.0':
+ resolution: {integrity: sha512-bCARdAsG+eEbOveSXcOAC7pVxA04I1ionzeMOwIYf5NX7SaS7zgn4Dgug2BSqoL+iyKeMNSorQjsFSHJHsCNwg==}
+
+ '@chevrotain/regexp-to-ast@13.2.0':
+ resolution: {integrity: sha512-kxpUkLtiaK09ph+dib+Yvm+3sWSnuwlo25gqfKRgTPNmF6H7uW4gVkMjaRdtnN783S9sM6vUNWh9CsxYX4QwKw==}
+
+ '@chevrotain/types@13.2.0':
+ resolution: {integrity: sha512-7qrYHRlKfWf2AvBfJi5eVOrKAkbxvMFsy/iIHTGIiApNcEecCaVIdR+mIQhyCBsTDJxBz4JWXHMo6krbhR9hVg==}
+
+ '@chevrotain/utils@13.2.0':
+ resolution: {integrity: sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q==}
+
'@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies:
@@ -1577,6 +1598,12 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
+ '@iconify/types@2.0.0':
+ resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+
+ '@iconify/utils@3.1.7':
+ resolution: {integrity: sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==}
+
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
@@ -1660,6 +1687,10 @@ packages:
resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==}
engines: {node: '>= 10'}
+ '@mermaid-js/parser@2.0.1':
+ resolution: {integrity: sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==}
+ engines: {node: '>=22.12.0'}
+
'@mistralai/mistralai@2.7.0':
resolution: {integrity: sha512-zCrx6MtNpcz+EL/Nqvg55QubSgmfwJfBmd3d3wSYfJGfnvlNnWcJYlxEW5xQpS3KR6A5qK9KUyRBwMDIWnyVGA==}
peerDependencies:
@@ -2160,6 +2191,99 @@ packages:
'@types/cross-spawn@6.0.6':
resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==}
+ '@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.1':
+ resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==}
+
+ '@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.4':
+ resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==}
+
+ '@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.12':
+ resolution: {integrity: sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==}
+
+ '@types/d3-shape@3.2.0':
+ resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==}
+
+ '@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.9':
+ resolution: {integrity: sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
@@ -2169,6 +2293,9 @@ packages:
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
'@types/hosted-git-info@3.0.5':
resolution: {integrity: sha512-Dmngh7U003cOHPhKGyA7LWqrnvcTyILNgNPmNCxlx7j8MIi54iBliiT8XqVLIQ3GchoOjVAyBzNJVyuaJjqokg==}
@@ -2222,6 +2349,9 @@ packages:
'@types/shell-quote@1.7.5':
resolution: {integrity: sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -2333,6 +2463,9 @@ packages:
'@ungap/structured-clone@1.4.0':
resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==}
+ '@upsetjs/venn.js@2.0.0':
+ resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
+
'@vitest/coverage-v8@4.1.11':
resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==}
peerDependencies:
@@ -2688,6 +2821,10 @@ packages:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ chevrotain@13.2.0:
+ resolution: {integrity: sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA==}
+ engines: {node: '>=22.0.0'}
+
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@@ -2733,6 +2870,14 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
+ 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'}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2769,6 +2914,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==}
+
croner@9.1.0:
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
engines: {node: '>=18.0'}
@@ -2789,6 +2940,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.34.3:
+ resolution: {integrity: sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==}
+ 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-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
@@ -2808,6 +3115,9 @@ packages:
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
+ dayjs@1.11.23:
+ resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}
+
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -2844,6 +3154,9 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ delaunator@5.1.0:
+ resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -2883,6 +3196,9 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
+ dompurify@3.4.17:
+ resolution: {integrity: sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==}
+
drizzle-orm@0.45.2:
resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==}
peerDependencies:
@@ -2988,6 +3304,9 @@ packages:
electron-to-chromium@1.5.439:
resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==}
+ elkjs@0.9.3:
+ resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -3037,6 +3356,9 @@ packages:
resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==}
engines: {node: '>= 0.4'}
+ es-toolkit@1.52.0:
+ resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==}
+
esbuild@0.28.2:
resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
engines: {node: '>=18'}
@@ -3422,6 +3744,9 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ hachure-fill@0.5.2:
+ resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+
handlebars@4.7.9:
resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
engines: {node: '>=0.4.7'}
@@ -3487,6 +3812,10 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
iconv-lite@0.7.3:
resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
engines: {node: '>=0.10.0'}
@@ -3513,6 +3842,9 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
+ import-meta-resolve@4.2.0:
+ resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
+
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -3535,6 +3867,13 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
+ 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'}
+
interpret@1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
@@ -3816,6 +4155,10 @@ packages:
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+ katex@0.16.47:
+ resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==}
+ hasBin: true
+
katex@0.18.7:
resolution: {integrity: sha512-h+UCwkZ+4Jz8WQ7MLGfj7UVFrRCizGb912fwF4luGdYsC5paYG1vx+jy+KRcC/XkpjGva/P7nAWuxNnPzRvzHw==}
hasBin: true
@@ -3823,6 +4166,9 @@ 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==}
+
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
@@ -3832,6 +4178,12 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
+ layout-base@1.0.2:
+ resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+ layout-base@2.0.1:
+ resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
+
lazystream@1.0.1:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
@@ -3854,6 +4206,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.identity@3.0.0:
resolution: {integrity: sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q==}
@@ -3887,6 +4242,11 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
+ marked@16.4.2:
+ resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
marked@18.0.12:
resolution: {integrity: sha512-LEm4ga2YeI2T3GVHj9b0BaDPPk93LLTHMFeMyQbNIzPxc8vCI0y/scy0ZA6z6lXKyT9j9Nhl/OC6ZYKYGuFScA==}
engines: {node: '>= 20'}
@@ -3908,6 +4268,10 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ mermaid@12.1.0:
+ resolution: {integrity: sha512-wlVCp+8eTupfCeeFvoZNNiTuHrvag0P2jz/ILgb/f/6jkVokUefOcujefi8qUe/j2asHiSePncVsz/xzzA80LQ==}
+ engines: {node: '>=22.12.0'}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -4147,6 +4511,9 @@ packages:
partial-json@0.1.7:
resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==}
+ 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'}
@@ -4230,6 +4597,12 @@ packages:
resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==}
engines: {node: '>=12.13.0'}
+ 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==}
+
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
@@ -4448,11 +4821,17 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
+ robust-predicates@3.0.3:
+ resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
+
rollup@4.63.1:
resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ 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'}
@@ -4460,6 +4839,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ rw@1.3.3:
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
safe-array-concat@1.1.4:
resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
engines: {node: '>=0.4'}
@@ -4685,6 +5067,9 @@ packages:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
+ stylis@4.4.0:
+ resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
+
sucrase@3.35.1:
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -4768,6 +5153,10 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
+ ts-dedent@2.3.0:
+ resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==}
+ engines: {node: '>=6.10'}
+
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -4785,6 +5174,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@4.23.13:
+ resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
tsx@4.23.15:
resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==}
engines: {node: '>=18.0.0'}
@@ -4877,6 +5271,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ uuid@14.0.3:
+ resolution: {integrity: sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==}
+ hasBin: true
+
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -5083,6 +5481,11 @@ snapshots:
'@alloc/quick-lru@5.3.0': {}
+ '@antfu/install-pkg@2.1.0':
+ dependencies:
+ package-manager-detector: 1.8.0
+ tinyexec: 1.3.1
+
'@anthropic-ai/sdk@0.91.1(zod@3.25.76)':
dependencies:
json-schema-to-ts: 3.1.1
@@ -5320,6 +5723,23 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
+ '@braintree/sanitize-url@7.1.2': {}
+
+ '@chevrotain/cst-dts-gen@13.2.0':
+ dependencies:
+ '@chevrotain/gast': 13.2.0
+ '@chevrotain/types': 13.2.0
+
+ '@chevrotain/gast@13.2.0':
+ dependencies:
+ '@chevrotain/types': 13.2.0
+
+ '@chevrotain/regexp-to-ast@13.2.0': {}
+
+ '@chevrotain/types@13.2.0': {}
+
+ '@chevrotain/utils@13.2.0': {}
+
'@dnd-kit/accessibility@3.1.1(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -5491,6 +5911,14 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
+ '@iconify/types@2.0.0': {}
+
+ '@iconify/utils@3.1.7':
+ dependencies:
+ '@antfu/install-pkg': 2.1.0
+ '@iconify/types': 2.0.0
+ import-meta-resolve: 4.2.0
+
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.3
@@ -5569,6 +5997,10 @@ snapshots:
'@mariozechner/clipboard-win32-x64-msvc': 0.3.9
optional: true
+ '@mermaid-js/parser@2.0.1':
+ dependencies:
+ '@chevrotain/types': 13.2.0
+
'@mistralai/mistralai@2.7.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/semantic-conventions': 1.43.0
@@ -5951,12 +6383,131 @@ snapshots:
dependencies:
'@types/node': 20.19.43
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.12
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.12
+
+ '@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.12
+
+ '@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.1':
+ 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.4': {}
+
+ '@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.12': {}
+
+ '@types/d3-shape@3.2.0':
+ 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.12
+
+ '@types/d3-zoom@3.0.9':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.12
+
+ '@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.1
+ '@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.4
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.12
+ '@types/d3-shape': 3.2.0
+ '@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.9
+
'@types/deep-eql@4.0.2': {}
'@types/diff@7.0.2': {}
'@types/estree@1.0.9': {}
+ '@types/geojson@7946.0.16': {}
+
'@types/hosted-git-info@3.0.5': {}
'@types/js-yaml@4.0.9': {}
@@ -6011,6 +6562,9 @@ snapshots:
'@types/shell-quote@1.7.5': {}
+ '@types/trusted-types@2.0.7':
+ optional: true
+
'@types/ws@8.18.1':
dependencies:
'@types/node': 20.19.43
@@ -6129,6 +6683,11 @@ snapshots:
'@ungap/structured-clone@1.4.0': {}
+ '@upsetjs/venn.js@2.0.0':
+ optionalDependencies:
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
'@vitest/coverage-v8@4.1.11(vitest@4.1.11)':
dependencies:
'@bcoe/v8-coverage': 1.0.2
@@ -6152,6 +6711,14 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.1
+ '@vitest/mocker@4.1.11(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1))':
+ dependencies:
+ '@vitest/spy': 4.1.11
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1)
+
'@vitest/mocker@4.1.11(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.0))':
dependencies:
'@vitest/spy': 4.1.11
@@ -6547,6 +7114,14 @@ snapshots:
chalk@5.6.2: {}
+ chevrotain@13.2.0:
+ dependencies:
+ '@chevrotain/cst-dts-gen': 13.2.0
+ '@chevrotain/gast': 13.2.0
+ '@chevrotain/regexp-to-ast': 13.2.0
+ '@chevrotain/types': 13.2.0
+ '@chevrotain/utils': 13.2.0
+
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@@ -6597,6 +7172,10 @@ snapshots:
commander@4.1.1: {}
+ commander@7.2.0: {}
+
+ commander@8.3.0: {}
+
concat-map@0.0.1: {}
confusing-browser-globals@1.0.11: {}
@@ -6620,6 +7199,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
+
croner@9.1.0: {}
cross-spawn@6.0.6:
@@ -6640,6 +7227,190 @@ snapshots:
csstype@3.2.3: {}
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.3):
+ dependencies:
+ cose-base: 1.0.3
+ cytoscape: 3.34.3
+
+ cytoscape-fcose@2.2.0(cytoscape@3.34.3):
+ dependencies:
+ cose-base: 2.2.0
+ cytoscape: 3.34.3
+
+ cytoscape@3.34.3: {}
+
+ 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-uri-to-buffer@4.0.1: {}
data-view-buffer@1.0.2:
@@ -6662,6 +7433,8 @@ snapshots:
dateformat@4.6.3: {}
+ dayjs@1.11.23: {}
+
debug@3.2.7:
dependencies:
ms: 2.1.3
@@ -6690,6 +7463,10 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ delaunator@5.1.0:
+ dependencies:
+ robust-predicates: 3.0.3
+
delayed-stream@1.0.0:
optional: true
@@ -6736,6 +7513,10 @@ snapshots:
dependencies:
esutils: 2.0.3
+ dompurify@3.4.17:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
drizzle-orm@0.45.2(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1):
optionalDependencies:
'@opentelemetry/api': 1.9.0
@@ -6755,6 +7536,8 @@ snapshots:
electron-to-chromium@1.5.439: {}
+ elkjs@0.9.3: {}
+
emoji-regex@8.0.0: {}
encodeurl@2.0.0: {}
@@ -6862,6 +7645,8 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
+ es-toolkit@1.52.0: {}
+
esbuild@0.28.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.28.2
@@ -7358,6 +8143,8 @@ snapshots:
graphemer@1.4.0: {}
+ hachure-fill@0.5.2: {}
+
handlebars@4.7.9:
dependencies:
minimist: 1.2.8
@@ -7431,6 +8218,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
iconv-lite@0.7.3:
dependencies:
safer-buffer: 2.1.2
@@ -7450,6 +8241,8 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
+ import-meta-resolve@4.2.0: {}
+
imurmurhash@0.1.4: {}
inflight@1.0.6:
@@ -7469,6 +8262,10 @@ snapshots:
hasown: 2.0.4
side-channel: 1.1.1
+ internmap@1.0.1: {}
+
+ internmap@2.0.3: {}
+
interpret@1.4.0: {}
interpret@3.1.1: {}
@@ -7723,6 +8520,10 @@ snapshots:
jwa: 2.0.1
safe-buffer: 5.2.1
+ katex@0.16.47:
+ dependencies:
+ commander: 8.3.0
+
katex@0.18.7:
dependencies:
commander: 15.0.0
@@ -7731,6 +8532,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ khroma@2.1.0: {}
+
kleur@3.0.3: {}
knip@6.38.0:
@@ -7749,6 +8552,10 @@ snapshots:
yaml: 2.9.1
zod: 4.6.5
+ layout-base@1.0.2: {}
+
+ layout-base@2.0.1: {}
+
lazystream@1.0.1:
dependencies:
readable-stream: 2.3.8
@@ -7770,6 +8577,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash-es@4.18.1: {}
+
lodash.identity@3.0.0:
optional: true
@@ -7802,6 +8611,8 @@ snapshots:
dependencies:
semver: 7.8.5
+ marked@16.4.2: {}
+
marked@18.0.12: {}
math-intrinsics@1.1.0: {}
@@ -7812,6 +8623,32 @@ snapshots:
merge2@1.4.1: {}
+ mermaid@12.1.0:
+ dependencies:
+ '@braintree/sanitize-url': 7.1.2
+ '@iconify/utils': 3.1.7
+ '@mermaid-js/parser': 2.0.1
+ '@types/d3': 7.4.3
+ '@upsetjs/venn.js': 2.0.0
+ chevrotain: 13.2.0
+ cytoscape: 3.34.3
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.3)
+ cytoscape-fcose: 2.2.0(cytoscape@3.34.3)
+ d3: 7.9.0
+ d3-sankey: 0.12.3
+ dagre-d3-es: 7.0.14
+ dayjs: 1.11.23
+ dompurify: 3.4.17
+ elkjs: 0.9.3
+ es-toolkit: 1.52.0
+ katex: 0.16.47
+ khroma: 2.1.0
+ marked: 16.4.2
+ roughjs: 4.6.6
+ stylis: 4.4.0
+ ts-dedent: 2.3.0
+ uuid: 14.0.3
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -8072,6 +8909,8 @@ snapshots:
partial-json@0.1.7: {}
+ path-data-parser@0.1.0: {}
+
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
@@ -8152,6 +8991,13 @@ snapshots:
pngjs@6.0.0: {}
+ 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
+
possible-typed-array-names@1.1.0: {}
postcss-import@15.1.0(postcss@8.5.28):
@@ -8393,6 +9239,8 @@ snapshots:
dependencies:
glob: 7.2.3
+ robust-predicates@3.0.3: {}
+
rollup@4.63.1:
dependencies:
'@types/estree': 1.0.9
@@ -8425,6 +9273,13 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.63.1
fsevents: 2.3.3
+ 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
@@ -8439,6 +9294,8 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ rw@1.3.3: {}
+
safe-array-concat@1.1.4:
dependencies:
call-bind: 1.0.9
@@ -8684,6 +9541,8 @@ snapshots:
strip-json-comments@5.0.3: {}
+ stylis@4.4.0: {}
+
sucrase@3.35.1:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
@@ -8793,6 +9652,8 @@ snapshots:
dependencies:
typescript: 5.9.3
+ ts-dedent@2.3.0: {}
+
ts-interface-checker@0.1.13: {}
tsconfig-paths-webpack-plugin@4.2.0:
@@ -8817,6 +9678,12 @@ snapshots:
tslib@2.8.1: {}
+ tsx@4.23.13:
+ dependencies:
+ esbuild: 0.28.2
+ optionalDependencies:
+ fsevents: 2.3.3
+
tsx@4.23.15:
dependencies:
esbuild: 0.28.2
@@ -8909,8 +9776,25 @@ snapshots:
util-deprecate@1.0.2: {}
+ uuid@14.0.3: {}
+
vary@1.1.2: {}
+ vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1):
+ dependencies:
+ esbuild: 0.28.2
+ fdir: 6.5.0(picomatch@4.0.7)
+ picomatch: 4.0.7
+ postcss: 8.5.28
+ rollup: 4.63.1
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ '@types/node': 20.19.43
+ fsevents: 2.3.3
+ jiti: 2.7.0
+ tsx: 4.23.13
+ yaml: 2.9.1
+
vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.0):
dependencies:
esbuild: 0.28.2
@@ -8971,6 +9855,35 @@ snapshots:
tsx: 4.23.15
yaml: 2.9.1
+ vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1)):
+ dependencies:
+ '@vitest/expect': 4.1.11
+ '@vitest/mocker': 4.1.11(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1))
+ '@vitest/pretty-format': 4.1.11
+ '@vitest/runner': 4.1.11
+ '@vitest/snapshot': 4.1.11
+ '@vitest/spy': 4.1.11
+ '@vitest/utils': 4.1.11
+ es-module-lexer: 2.3.2
+ expect-type: 1.4.0
+ magic-string: 0.30.21
+ obug: 2.2.1
+ pathe: 2.0.3
+ picomatch: 4.0.7
+ std-env: 4.2.0
+ tinybench: 2.9.0
+ tinyexec: 1.3.1
+ tinyglobby: 0.2.17
+ tinyrainbow: 3.1.1
+ vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.1)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@opentelemetry/api': 1.9.0
+ '@types/node': 20.19.43
+ '@vitest/coverage-v8': 4.1.11(vitest@4.1.11)
+ transitivePeerDependencies:
+ - msw
+
vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.11
diff --git a/release/public-source.json b/release/public-source.json
index d1ca5503c..e7657c820 100644
--- a/release/public-source.json
+++ b/release/public-source.json
@@ -3657,6 +3657,7 @@
"packages/webui/src/client/components/transcript/TranscriptSkeletons.tsx",
"packages/webui/src/client/components/transcript/TurnNavigator.tsx",
"packages/webui/src/client/components/transcript/markdown.tsx",
+ "packages/webui/src/client/components/transcript/webui-mermaid.tsx",
"packages/webui/src/client/components/workspace/ReviewSettingsPage.tsx",
"packages/webui/src/client/components/workspace/WebuiWorkspaceDirectoryBrowser.tsx",
"packages/webui/src/client/components/workspace/WorkspaceArchiveView.tsx",
@@ -3924,6 +3925,7 @@
"packages/webui/test/unit/markdown-autolink-double-anchor.test.tsx",
"packages/webui/test/unit/markdown-codeblock-c1-highlight.test.tsx",
"packages/webui/test/unit/markdown-codeblock-c2-line-numbers.test.tsx",
+ "packages/webui/test/unit/markdown-mermaid.test.tsx",
"packages/webui/test/unit/message-attachments.test.tsx",
"packages/webui/test/unit/message-parts.test.ts",
"packages/webui/test/unit/model-picker-cascade.test.ts",
diff --git a/scripts/build-webui.mjs b/scripts/build-webui.mjs
index e1026271a..cb7044b4c 100644
--- a/scripts/build-webui.mjs
+++ b/scripts/build-webui.mjs
@@ -113,6 +113,16 @@ const client = await build({
metafile: true,
jsx: "automatic",
loader: { ".html": "text" },
+ // Code splitting is load-bearing, not an optimisation: without it esbuild
+ // inlines every dynamically imported module into `client.js`, and the
+ // transcript lazily loads the diagram library (`webui-mermaid.tsx`'s
+ // `import("mermaid")`) — the entry bundle would grow by the library's full
+ // source instead of a chunk that only downloads when a diagram appears.
+ // Chunks land under `assets/`, the prefix the loopback service's static
+ // resolver already serves alongside the entry (see `resolveClientAsset` in
+ // `packages/webui/src/server/http/client-assets.ts`).
+ splitting: true,
+ chunkNames: "assets/[name]-[hash]",
// Same workspace resolution as the server build: the browser bundle
// imports `@mavis/shared/daily-signin` (the check-in card shares the
// desktop's validators), and a source checkout has no package dist/ to
diff --git a/scripts/package-webui-npm.mjs b/scripts/package-webui-npm.mjs
index efe5c069b..ba3abc2a8 100644
--- a/scripts/package-webui-npm.mjs
+++ b/scripts/package-webui-npm.mjs
@@ -92,6 +92,10 @@ const client = await build({
metafile: true,
jsx: "automatic",
loader: { ".html": "text" },
+ // Same reason as `build-webui.mjs`: a dynamic `import()` must leave the
+ // entry as its own chunk under `assets/`, not be inlined into `client.js`.
+ splitting: true,
+ chunkNames: "assets/[name]-[hash]",
plugins: [workspacePlugin],
logLevel: "info",
});
diff --git a/test/vitest-suites.json b/test/vitest-suites.json
index d591e5138..325ee94ce 100644
--- a/test/vitest-suites.json
+++ b/test/vitest-suites.json
@@ -336,6 +336,7 @@
"packages/webui/test/unit/transcript-scroll.test.ts",
"packages/webui/test/unit/markdown-codeblock-c1-highlight.test.tsx",
"packages/webui/test/unit/markdown-codeblock-c2-line-numbers.test.tsx",
+ "packages/webui/test/unit/markdown-mermaid.test.tsx",
"packages/webui/test/unit/markdown-autolink-double-anchor.test.tsx",
"packages/webui/test/unit/transcript-file-anchor-anchors.test.tsx",
"packages/webui/test/unit/transcript-interrupt-frames.test.ts",
diff --git a/test/webui-browser/server.mjs b/test/webui-browser/server.mjs
index d0392f71d..e5c30b779 100644
--- a/test/webui-browser/server.mjs
+++ b/test/webui-browser/server.mjs
@@ -8,28 +8,55 @@ import { installFixtureTransport } from "./fixture.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../dist-webui/client");
const port = 4179;
const serverId = process.env.WEBUI_BROWSER_SERVER_ID ?? randomUUID();
+
+/** The entry files the built client references by name. */
+const ROOT_ASSETS = ["index.html", "client.js", "styles.css"];
+
+/**
+ * Resolve a request path to a file under the built client directory, or
+ * `undefined` when it falls outside the served set.
+ *
+ * The set mirrors the loopback service's static resolver (`resolveClientAsset`
+ * in `packages/webui/src/server/http/client-assets.ts`): the three entry names
+ * plus the `assets/` prefix. That prefix is where the client build emits its
+ * lazily loaded chunks — the transcript pulls the diagram library in through
+ * a dynamic `import()`, and `scripts/build-webui.mjs` keeps it out of
+ * `client.js` as an `assets/` chunk — so the fixture must serve what the real
+ * service serves or the diagram tests would 404 on the chunk.
+ */
+function resolveServedFile(pathname) {
+ const name = pathname === "/" || pathname === "/index.html" ? "index.html" : pathname.slice(1);
+ if (ROOT_ASSETS.includes(name)) return path.join(root, name);
+ if (!name.startsWith("assets/")) return undefined;
+ const relative = name.slice("assets/".length);
+ if (!relative || relative.includes("\\") || relative.split("/").includes("..")) return undefined;
+ const candidate = path.resolve(root, "assets", relative);
+ return candidate.startsWith(path.resolve(root) + path.sep) ? candidate : undefined;
+}
+
const server = createServer(async (request, response) => {
const pathname = new URL(request.url ?? "/", `http://127.0.0.1:${port}`).pathname;
if (pathname === "/health") {
response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ status: "ok", serverId }));
return;
}
- const name = pathname === "/" || pathname === "/index.html" ? "index.html" : path.basename(pathname);
- if (!["index.html", "client.js", "styles.css"].includes(name)) {
+ const file = resolveServedFile(pathname);
+ if (!file) {
response.writeHead(404).end("not found");
return;
}
try {
- let body = await readFile(path.join(root, name));
- if (name === "index.html") {
+ let body = await readFile(file);
+ if (path.basename(file) === "index.html") {
const html = body.toString().replace(
"",
``,
);
body = Buffer.from(html);
}
- const contentType = name.endsWith(".js") ? "text/javascript; charset=utf-8" : name.endsWith(".css") ? "text/css; charset=utf-8" : "text/html; charset=utf-8";
- response.writeHead(200, { "content-type": contentType, "cache-control": "no-store" }).end(body);
+ const contentType = file.endsWith(".js") ? "text/javascript; charset=utf-8" : file.endsWith(".css") ? "text/css; charset=utf-8" : "text/html; charset=utf-8";
+ response.writeHead(200, { "content-type": contentType, "cache-control": "no-store" });
+ response.end(body);
} catch {
response.writeHead(404).end("built WebUI artifact not found; run pnpm build:webui first");
}
diff --git a/test/webui-browser/transcript.spec.mjs b/test/webui-browser/transcript.spec.mjs
index aa8eb6d22..9e21ee85b 100644
--- a/test/webui-browser/transcript.spec.mjs
+++ b/test/webui-browser/transcript.spec.mjs
@@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
-import { configureFixture, openApp, switchSession, test } from "./harness.mjs";
+import { configureFixture, openApp, switchSession, test, assistantBody } from "./harness.mjs";
test.beforeEach(async ({ page }) => {
page.on("pageerror", (error) => console.error("BROWSER_PAGE_ERROR", error.stack ?? error.message));
@@ -166,3 +166,35 @@ test("a late questionnaire.dismiss cannot revive the turn after a local skip", a
await expect(page.getByTestId("questionnaire-composer")).toHaveCount(0);
await expect(page.getByText("思考中…")).toHaveCount(0);
});
+
+// A Mermaid fence renders to a real SVG diagram — the acceptance table's
+// "markdown-图表(mmd)" row, end to end in a browser.
+//
+// This case carries the reverse verification the task book demands: the unit
+// suite stubs the diagram library, so it cannot see the wiring between the
+// fence branch and the renderer. Remove the Mermaid branch from
+// `markdown.tsx` and the fence falls through to the plain code path — no
+// `svg` ever appears and the assertion below goes red, which is exactly the
+// property the task book asks a reviewer to check by deleting the branch.
+test("a mermaid fence renders to an SVG diagram", async ({ page }) => {
+ await configureFixture(page, () => {
+ window.__fixture.setPage("A", {
+ messages: [
+ {
+ msgId: "mermaid-turn",
+ role: "assistant",
+ msgContent: "```mermaid\ngraph TD; Start-->Finish;\n```",
+ timestamp: 1_700_000_000_100,
+ },
+ ],
+ hasMore: false,
+ });
+ });
+ await openApp(page, "#session=A");
+ const body = assistantBody(page, "mermaid-turn");
+ // The diagram library is a lazily loaded chunk, fetched on the first fence.
+ // The budget sits well above the renderer's own timeout, so a slow machine
+ // fails here loudly instead of silently falling back to the plain fence.
+ await expect(body.locator(".webui-mermaid svg")).toBeVisible({ timeout: 20_000 });
+ await expect(body.locator(".webui-code-block")).toHaveCount(0);
+});
From 19393940d43d40e840cb04443ef8f55f75cf24db Mon Sep 17 00:00:00 2001
From: stevenjj33 <75509501+stevenjj33@users.noreply.github.com>
Date: Wed, 14 Oct 2026 00:50:56 +0800
Subject: [PATCH 2/3] fix(webui): give the adopted composer store the storage
adapter again
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
e40a63f moved composer-store ownership into the application and made the
shell adopt the composition root's application (A4-s2). The root built
its application without a `composer` dep, so `create-application` fell
back to `createWebuiComposerStore()` — storage-less by design, because
the application layer may not import infrastructure. The shell's own
storage-backed store (`providedApplication?.composer ?? create(...)`)
became dead code on the live path, and with it every draft: typing
still worked and switching sessions still restored the in-memory draft,
but nothing ever reached localStorage, so a reload lost them all.
Verified on webui @ 6a455a2 before this change: the drafts spec failed
deterministically (3 runs, same failure — after reload the composer is
empty and `webui.composer.state.v1` is null even before the reload), and
a probe showed the key was never written. With the injection: 7/7 in
composer.spec.mjs, 97/97 in the full browser suite.
The composition root now injects the same storage adapter the shell
would have built, exactly as the shell already does for `unreadStorage`
when it builds its own application. The unread controller has the same
gap at the same call site (no failing gate covers it); left for a
follow-up rather than widening this fix.
---
packages/webui/src/client/main.tsx | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/packages/webui/src/client/main.tsx b/packages/webui/src/client/main.tsx
index 08178c953..4ce0d2bcc 100644
--- a/packages/webui/src/client/main.tsx
+++ b/packages/webui/src/client/main.tsx
@@ -13,13 +13,14 @@ import { NotFound } from "./components/shell/NotFound.js";
import { ArchonPage } from "./components/shell/ArchonPage.js";
import { WebuiErrorBoundary } from "./components/shell/WebuiErrorBoundary.js";
import { WebuiStartupFallback } from "./components/shell/StartupFallback.js";
-import { WebuiBrowserCapabilitiesProvider } from "./bindings/browser-capabilities.js";
+import { WebuiBrowserCapabilitiesProvider, useWebuiBrowserCapabilities } from "./bindings/browser-capabilities.js";
import { createWebuiBrowserDom } from "./infrastructure/browser-dom.js";
import {
createWebuiBrowserStorage,
getWebuiBrowserStorage,
} from "./infrastructure/storage.js";
import { createWebuiApplication } from "./application/create-application.js";
+import { createWebuiComposerStore } from "./application/composer/composer-store.js";
import { createWebuiEventEffectsRegistry } from "./application/turn/event-effects-registry.js";
import { createWebuiPortSlices } from "./application/port-slices.js";
import { createWebuiOpenEventChannel } from "./bindings/event-channel-adapter.js";
@@ -69,6 +70,17 @@ function WebuiClientRoot({
() => createWebuiEventEffectsRegistry(),
[],
);
+ // The same storage adapter the shell would have built for its own composer
+ // store. The application layer may not import infrastructure, so the
+ // composition root injects it — without this the adopted composer store has
+ // no storage at all and every draft dies with the page (regression from
+ // e40a63f: the root stopped handing the shell a store to build, and the
+ // application's own default is storage-less by design).
+ const { storage } = useWebuiBrowserCapabilities();
+ const composerStore = React.useMemo(
+ () => createWebuiComposerStore({ storage }),
+ [storage],
+ );
const slices = React.useMemo(
() => createWebuiPortSlices(transport),
[transport],
@@ -85,6 +97,7 @@ function WebuiClientRoot({
// own view reactions on top and registers them through
// `setEventEffects`, because it renders after this instance exists.
effects: effectsRegistry.asWebuiEventEffects(),
+ composer: composerStore,
turns: {
resumeSession: transport.resumeSession ?? (async () => {}),
...(transport.loadMessages
@@ -96,7 +109,7 @@ function WebuiClientRoot({
? { loadMessages: transport.loadMessages }
: {}),
}),
- [effectsRegistry, transport],
+ [composerStore, effectsRegistry, transport],
);
React.useEffect(() => () => application.dispose(), [application]);
From 517402f866685e674974c6bb6a7eeb1ee811df20 Mon Sep 17 00:00:00 2001
From: stevenjj33 <75509501+stevenjj33@users.noreply.github.com>
Date: Wed, 14 Oct 2026 13:02:27 +0800
Subject: [PATCH 3/3] ci: retrigger