diff --git a/packages/webui/package.json b/packages/webui/package.json index be09c874b..1f0729cd1 100644 --- a/packages/webui/package.json +++ b/packages/webui/package.json @@ -36,6 +36,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/features/transcript/ui/markdown.public.tsx b/packages/webui/src/client/features/transcript/ui/markdown.public.tsx index 8d235e64a..f798af6d9 100644 --- a/packages/webui/src/client/features/transcript/ui/markdown.public.tsx +++ b/packages/webui/src/client/features/transcript/ui/markdown.public.tsx @@ -1,5 +1,6 @@ import { marked, type Token } from "marked"; import katex from "katex"; +import { isWebuiMermaidFence, WebuiMermaid } from "./webui-mermaid.js"; import hljs from "highlight.js/lib/core"; import bash from "highlight.js/lib/languages/bash"; import c from "highlight.js/lib/languages/c"; @@ -267,6 +268,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/features/transcript/ui/transcript.css b/packages/webui/src/client/features/transcript/ui/transcript.css index 44d1e838f..18382395e 100644 --- a/packages/webui/src/client/features/transcript/ui/transcript.css +++ b/packages/webui/src/client/features/transcript/ui/transcript.css @@ -788,6 +788,32 @@ .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 — drawn with the app's + current theme, so a diagram on a dark page is a dark diagram — and 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/src/client/features/transcript/ui/webui-mermaid.tsx b/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx new file mode 100644 index 000000000..d5fbfa0b7 --- /dev/null +++ b/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx @@ -0,0 +1,293 @@ +// webui-mermaid.tsx — Mermaid diagram rendering for the transcript. +// +// The acceptance table carried a "markdown-图表(mmd) 通过" row while 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. +// +// Four 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.
+//   4. The rendered SVG is sanitized before it is injected, and the diagram is
+//      drawn with the theme the app is actually showing. Both are load-bearing
+//      — see the two sections below.
+
+import { useEffect, useState, type ReactElement } from "react";
+
+import { useWebuiBrowserCapabilities } from "../../../bindings/browser-capabilities.js";
+
+/** 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.public.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());
+}
+
+/* Theme ------------------------------------------------------------------ */
+
+/** The two themes the app can be showing. */
+export type WebuiMermaidTheme = "light" | "dark";
+
+/**
+ * The theme the app is currently showing.
+ *
+ * Read through the injected DOM adapter, not from `document`: the dependency
+ * rules confine the ambient document to the platform layer, and a component
+ * that reaches for it directly is a violation the gate reports. The adapter
+ * reads the `dark` class the app's own appearance effect puts on the
+ * document element — the theme the reader is looking at, including a
+ * preference of "system" resolved at this moment.
+ */
+export function readWebuiDocumentTheme(
+  readTheme: () => "dark" | "light",
+): WebuiMermaidTheme {
+  try {
+    return readTheme();
+  } catch {
+    // No DOM (SSR, a Node test): light is the library's own default.
+    return "light";
+  }
+}
+
+/* Sanitization ----------------------------------------------------------- */
+
+/**
+ * Strip the markup that must never reach the DOM from a rendered diagram.
+ *
+ * Mermaid runs with `securityLevel: "strict"`, which is its own sanitizing
+ * mode and the primary control: labels are escaped and the library emits no
+ * scripts. This filter is the second layer, and it exists because the input
+ * is model output — a diagram body can be steered by whatever the model read
+ * (a repository file, a web page) — and because a rendered SVG is injected
+ * with `dangerouslySetInnerHTML`, where a ``));
   await writeFile(path.join(clientDir, name), body);
 }
+// The split bundle's chunks live under assets/ — the entry imports them, so a
+// copy without the directory cannot boot at all (#webui-root never appears).
+// Copy it wholesale, matching what the loopback resolver serves and the
+// packaged release ships.
+await cp(path.join(root, "dist-webui/client/assets"), path.join(clientDir, "assets"), { recursive: true });
 const assembled = await createWebuiRuntimeHost({
   dataDir: directory, appVersion: "webui-native-acceptance",
   mcodeTools: { prepare: async () => ({ requested: false, ready: false, category: "disabled" as const,
diff --git a/packages/webui/test/browser/transcript.spec.mjs b/packages/webui/test/browser/transcript.spec.mjs
index fcd9e8bac..0c7e2a270 100644
--- a/packages/webui/test/browser/transcript.spec.mjs
+++ b/packages/webui/test/browser/transcript.spec.mjs
@@ -236,3 +236,115 @@ test("a session switch reads the page once per need, not once per reader", async
   await page.waitForTimeout(400);
   expect(await reads()).toBe(1);
 });
+
+/* Mermaid fences in the transcript, against the real library.
+ *
+ * The unit suite mocks `mermaid` (the Node environment has no DOM for it), so
+ * this is where the library actually runs: the diagram renders to SVG, the
+ * chunk loads on demand, and the failure path falls back to the plain fence.
+ * The theme case matters because Mermaid bakes its palette into the SVG — a
+ * diagram drawn with the light palette is a light diagram even on a dark
+ * page, so the renderer has to follow the app's theme. */
+const MERMAID_DIAGRAM = "graph TD;\n  A[开始] --> B[结束];";
+
+async function stageTranscript(page, messages) {
+  // `addInitScript` with an argument, not `configureFixture`: the fixture
+  // setup is serialized with `toString()`, so it cannot close over the
+  // messages.
+  await page.addInitScript((value) => {
+    window.__WEBUI_FIXTURE_SETUP__ ??= [];
+    window.__WEBUI_FIXTURE_SETUP__.push(() => window.__fixture.setPage("A", { messages: value, hasMore: false }));
+  }, messages);
+}
+
+test("a mermaid fence renders as a diagram, not as text", async ({ page }) => {
+  await stageTranscript(page, [
+    { msgId: "m-1", role: "assistant", msgContent: "```mermaid\n" + MERMAID_DIAGRAM + "\n```", timestamp: 1_700_000_000_001 },
+  ]);
+  await openApp(page, "#session=A");
+
+  // The real library, in a real browser: the fence becomes an SVG.
+  const diagram = page.locator(".webui-mermaid svg");
+  await expect(diagram).toBeVisible({ timeout: 20_000 });
+  // …and the diagram is the fence's own content, not the source text.
+  await expect(page.locator(".webui-mermaid pre")).toHaveCount(0);
+  await expect(diagram.locator("text").first()).toContainText("开始");
+});
+
+test("an invalid diagram falls back to the plain fence", async ({ page }) => {
+  await stageTranscript(page, [
+    { msgId: "m-2", role: "assistant", msgContent: "```mermaid\nthis is not a diagram at all {{{\n```", timestamp: 1_700_000_000_001 },
+  ]);
+  await openApp(page, "#session=A");
+
+  // The failure path: no SVG, and the original text back in a plain fence —
+  // the markup the fence had before Mermaid existed.
+  await expect(page.locator(".webui-mermaid svg")).toHaveCount(0);
+  const fallback = page.locator(".webui-code-block code");
+  await expect(fallback).toBeVisible();
+  await expect(fallback).toContainText("this is not a diagram at all");
+});
+
+test("a diagram follows the app's theme", async ({ page }) => {
+  await stageTranscript(page, [
+    { msgId: "m-3", role: "assistant", msgContent: "```mermaid\n" + MERMAID_DIAGRAM + "\n```", timestamp: 1_700_000_000_001 },
+  ]);
+  await openApp(page, "#session=A");
+  await expect(page.locator(".webui-mermaid svg")).toBeVisible({ timeout: 20_000 });
+
+  // Mermaid bakes its palette into the SVG it returns, so a diagram drawn
+  // with the light palette is a light diagram even on a dark page. The
+  // comparison is between two runs rather than against a literal colour, so
+  // it survives a palette change in the library.
+  const readPalette = () =>
+    page.locator(".webui-mermaid svg").evaluate((node) => node.querySelector("style")?.textContent ?? "");
+  const lightPalette = await readPalette();
+
+  // The app's own preference — not a hand-set class: the app applies the
+  // stored theme to `` at boot, and the renderer reads that class when
+  // it draws.
+  await page.addInitScript(() => {
+    window.localStorage.setItem("webui-theme", "dark");
+  });
+  await page.reload();
+  await expect(page.locator(".webui-mermaid svg")).toBeVisible({ timeout: 20_000 });
+  await expect(page.locator("html")).toHaveClass(/dark/);
+
+  expect(await readPalette()).not.toBe(lightPalette);
+});
+
+test("a wide diagram fits its container", async ({ page }) => {
+  // A wide diagram must not push the transcript sideways: the SVG scales with
+  // the container (`max-width: 100%`, `height: auto`).
+  await stageTranscript(page, [
+    {
+      msgId: "m-4",
+      role: "assistant",
+      msgContent: "```mermaid\ngraph LR;\n" + Array.from({ length: 12 }, (_, index) => `  N${index}[节点${index}]`).join("\n") + ";\n```",
+      timestamp: 1_700_000_000_001,
+    },
+  ]);
+  await openApp(page, "#session=A");
+  const diagram = page.locator(".webui-mermaid svg");
+  await expect(diagram).toBeVisible({ timeout: 20_000 });
+  const sizes = await diagram.evaluate((node) => ({
+    svg: node.getBoundingClientRect().width,
+    container: node.parentElement?.getBoundingClientRect().width ?? 0,
+  }));
+  expect(sizes.svg).toBeLessThanOrEqual(sizes.container + 1);
+});
+
+test("a half-streamed fence falls back instead of breaking the transcript", async ({ page }) => {
+  // A fence that has not finished arriving (the model is still streaming) is
+  // an incomplete code block: the diagram cannot render, and the fence must
+  // fall back to the plain source rather than leave the transcript broken.
+  await stageTranscript(page, [
+    { msgId: "m-5", role: "assistant", msgContent: "```mermaid\ngraph TD;\n  A[开始] --> B[结", timestamp: 1_700_000_000_001 },
+  ]);
+  await openApp(page, "#session=A");
+
+  await expect(page.locator(".webui-mermaid svg")).toHaveCount(0);
+  await expect(page.locator(".webui-code-block code")).toBeVisible();
+  // The transcript is still alive: the rest of the message is on screen.
+  await expect(page.getByPlaceholder("输入消息…(输入 / 唤起命令)")).toBeVisible();
+});
diff --git a/packages/webui/test/gates/fixtures/path-metadata-f00.json b/packages/webui/test/gates/fixtures/path-metadata-f00.json
index 6a887e75b..44c982299 100644
--- a/packages/webui/test/gates/fixtures/path-metadata-f00.json
+++ b/packages/webui/test/gates/fixtures/path-metadata-f00.json
@@ -2397,6 +2397,15 @@
       "business": "transcript",
       "home": "target"
     },
+    {
+      "path": "client/features/transcript/ui/webui-mermaid.tsx",
+      "layers": [
+        "bindings"
+      ],
+      "role": "feature-ui",
+      "business": "transcript",
+      "home": "target"
+    },
     {
       "path": "client/features/turn/application/composer-submission.ts",
       "layers": [
@@ -2920,7 +2929,7 @@
       "home": "target"
     },
     {
-      "path": "client/features/workspace/ui/WorkspaceProgressPanel.tsx",
+      "path": "client/features/workspace/ui/WorkspacePanels.tsx",
       "layers": [
         "bindings"
       ],
@@ -2929,7 +2938,7 @@
       "home": "target"
     },
     {
-      "path": "client/features/workspace/ui/WorkspacePanels.tsx",
+      "path": "client/features/workspace/ui/WorkspaceProgressPanel.tsx",
       "layers": [
         "bindings"
       ],
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..d69fed57a
--- /dev/null
+++ b/packages/webui/test/unit/markdown-mermaid.test.tsx
@@ -0,0 +1,296 @@
+// 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/features/transcript/ui/markdown.public.js";
+import {
+  isWebuiMermaidFence,
+  readWebuiDocumentTheme,
+  renderWebuiMermaidSvg,
+  sanitizeWebuiMermaidSvg,
+  WebuiMermaidBlock,
+  type WebuiMermaidState,
+} from "../../src/client/features/transcript/ui/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 (
+    `
` + + `
${body}
` + + `
` + ); +} + +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); + } + }); +}); + +describe("the SVG that reaches the DOM", () => { + /* The diagram body is model output — it can carry whatever the model read — + and the rendered SVG is injected with `dangerouslySetInnerHTML`. Mermaid + runs in `securityLevel: "strict"`, which is the primary control; this + filter is the second layer, and these cases are what make it a property + of the component rather than of the library version. */ + it("strips scripts, event handlers and script-ish URLs", () => { + const hostile = [ + '', + '', + 'x', + '', + "", + ].join(""); + const clean = sanitizeWebuiMermaidSvg(hostile); + expect(clean).not.toContain(" { + const withForeign = ''; + const clean = sanitizeWebuiMermaidSvg(withForeign); + expect(clean).not.toContain("foreignObject"); + expect(clean).not.toContain(""); + }); + + it("leaves an ordinary diagram untouched", () => { + const plain = '开始'; + expect(sanitizeWebuiMermaidSvg(plain)).toBe(plain); + }); +}); + +describe("the theme the diagram is drawn with", () => { + it("takes the theme from the injected adapter, and light when it cannot", () => { + // The adapter is the only way in — the dependency rules confine the + // ambient document to the platform layer, so the component reads the + // theme through it. The fallback is what keeps a server render (or a + // test) from throwing; the DOM path itself is covered by the browser + // case, which draws the same diagram under both themes. + expect(readWebuiDocumentTheme(() => "dark")).toBe("dark"); + expect(readWebuiDocumentTheme(() => "light")).toBe("light"); + expect( + readWebuiDocumentTheme(() => { + throw new Error("no document"); + }), + ).toBe("light"); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95333a119..43d9efd74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -960,6 +960,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 @@ -1225,6 +1228,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 @@ -1356,6 +1362,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: @@ -1589,6 +1613,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'} @@ -1672,6 +1702,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: @@ -2180,6 +2214,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==} @@ -2189,6 +2316,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==} @@ -2242,6 +2372,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==} @@ -2353,6 +2486,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: @@ -2708,6 +2844,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'} @@ -2753,6 +2893,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==} @@ -2789,6 +2937,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'} @@ -2809,6 +2963,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.4: + resolution: {integrity: sha512-msY99tMY8iWTg+sIPGfSqDcPNOt4xcj+v/K/7qUFaCUxxlqnMkUYLBjRUzn9W5CU/R2hrmtJa5+UtovrEM2ctg==} + 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'} @@ -2828,6 +3138,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: @@ -2864,6 +3177,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'} @@ -2903,6 +3219,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: @@ -3008,6 +3327,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==} @@ -3057,6 +3379,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'} @@ -3442,6 +3767,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'} @@ -3507,6 +3835,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'} @@ -3533,6 +3865,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'} @@ -3555,6 +3890,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'} @@ -3836,6 +4178,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 @@ -3843,6 +4189,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'} @@ -3852,6 +4201,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'} @@ -3874,6 +4229,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==} @@ -3907,6 +4265,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'} @@ -3928,6 +4291,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'} @@ -4167,6 +4534,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'} @@ -4253,6 +4623,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'} @@ -4471,11 +4847,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'} @@ -4483,6 +4865,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'} @@ -4708,6 +5093,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'} @@ -4791,6 +5179,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==} @@ -4905,6 +5297,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'} @@ -5115,6 +5511,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 @@ -5352,6 +5753,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 @@ -5523,6 +5941,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 @@ -5601,6 +6027,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 @@ -5990,12 +6420,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': {} @@ -6050,6 +6599,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 @@ -6168,6 +6720,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 @@ -6594,6 +7151,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 @@ -6644,6 +7209,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: {} @@ -6667,6 +7236,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: @@ -6687,6 +7264,190 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.4): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.4 + + cytoscape-fcose@2.2.0(cytoscape@3.34.4): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.4 + + cytoscape@3.34.4: {} + + 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: @@ -6709,6 +7470,8 @@ snapshots: dateformat@4.6.3: {} + dayjs@1.11.23: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -6737,6 +7500,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 @@ -6783,6 +7550,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 @@ -6802,6 +7573,8 @@ snapshots: electron-to-chromium@1.5.439: {} + elkjs@0.9.3: {} + emoji-regex@8.0.0: {} encodeurl@2.0.0: {} @@ -6909,6 +7682,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 @@ -7405,6 +8180,8 @@ snapshots: graphemer@1.4.0: {} + hachure-fill@0.5.2: {} + handlebars@4.7.9: dependencies: minimist: 1.2.8 @@ -7478,6 +8255,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 @@ -7497,6 +8278,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: @@ -7516,6 +8299,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: {} @@ -7770,6 +8557,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 @@ -7778,6 +8569,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kleur@3.0.3: {} knip@6.38.0: @@ -7796,6 +8589,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 @@ -7817,6 +8614,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.identity@3.0.0: optional: true @@ -7849,6 +8648,8 @@ snapshots: dependencies: semver: 7.8.5 + marked@16.4.2: {} + marked@18.0.12: {} math-intrinsics@1.1.0: {} @@ -7859,6 +8660,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.4 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.4) + cytoscape-fcose: 2.2.0(cytoscape@3.34.4) + 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 @@ -8119,6 +8946,8 @@ snapshots: partial-json@0.1.7: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -8201,6 +9030,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): @@ -8442,6 +9278,8 @@ snapshots: dependencies: glob: 7.2.3 + robust-predicates@3.0.3: {} + rollup@4.63.1: dependencies: '@types/estree': 1.0.9 @@ -8474,6 +9312,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 @@ -8488,6 +9333,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-array-concat@1.1.4: dependencies: call-bind: 1.0.9 @@ -8733,6 +9580,8 @@ snapshots: strip-json-comments@5.0.3: {} + stylis@4.4.0: {} + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -8842,6 +9691,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: @@ -8964,6 +9815,8 @@ 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): diff --git a/release/public-source.json b/release/public-source.json index b38169c04..6bf278b9f 100644 --- a/release/public-source.json +++ b/release/public-source.json @@ -3934,6 +3934,7 @@ "packages/webui/src/client/features/transcript/ui/markdown.public.tsx", "packages/webui/src/client/features/transcript/ui/transcript-widgets.css", "packages/webui/src/client/features/transcript/ui/transcript.css", + "packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx", "packages/webui/src/client/features/turn/application/composer-submission.ts", "packages/webui/src/client/features/turn/application/delivery-disposers.ts", "packages/webui/src/client/features/turn/application/effect-reducer.ts", @@ -4299,6 +4300,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-effort-scale.test.ts", diff --git a/scripts/build-webui.mjs b/scripts/build-webui.mjs index 3d9a7881a..77441300f 100644 --- a/scripts/build-webui.mjs +++ b/scripts/build-webui.mjs @@ -110,6 +110,17 @@ const client = await build({ bundle: true, format: "esm", platform: "browser", + // Code splitting is load-bearing, not an optimisation: without it esbuild + // inlines every dynamically imported module into `client.js`. The + // transcript lazily loads the diagram library (`webui-mermaid.tsx`'s + // `import("mermaid")`), and the composer's slash palette is already a + // dynamic import — inlining either would put the whole library in the + // entry. 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]", + target: "es2022", outdir: path.join(outdir, "client"), metafile: true, diff --git a/scripts/package-webui-npm.mjs b/scripts/package-webui-npm.mjs index 4cebd05e7..b162ca2cf 100644 --- a/scripts/package-webui-npm.mjs +++ b/scripts/package-webui-npm.mjs @@ -86,6 +86,17 @@ const client = await build({ bundle: true, format: "esm", platform: "browser", + // Code splitting is load-bearing, not an optimisation: without it esbuild + // inlines every dynamically imported module into `client.js`. The + // transcript lazily loads the diagram library (`webui-mermaid.tsx`'s + // `import("mermaid")`), and the composer's slash palette is already a + // dynamic import — inlining either would put the whole library in the + // entry. 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]", + target: "es2022", outdir: path.join(outdir, "client"), metafile: true, diff --git a/test/vitest-suites.json b/test/vitest-suites.json index c6deda91a..2e8524cb3 100644 --- a/test/vitest-suites.json +++ b/test/vitest-suites.json @@ -299,6 +299,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/runtime-warning-rendering.test.tsx",