diff --git a/cmd/fakeagent/main.go b/cmd/fakeagent/main.go index d24cac9..ae43e5c 100644 --- a/cmd/fakeagent/main.go +++ b/cmd/fakeagent/main.go @@ -182,6 +182,8 @@ func brain(system, prompt string) string { list = append(list, map[string]any{"type": "reply", "of": id, "reply": "收到外部事件:" + firstRunes(text, 60)}) case strings.Contains(text, "代码示例"): list = append(list, map[string]any{"type": "reply", "of": id, "reply": "可以这样跑测试:\n\n```sh\nmake test\npnpm -C frontend test\n```"}) + case strings.Contains(text, "流程图"): + list = append(list, map[string]any{"type": "reply", "of": id, "reply": "消息是这样流转的:\n\n```mermaid\nflowchart LR\n A[收到消息] --> B{要动手吗}\n B -->|是| C[派给 worker]\n B -->|否| D[直接回复]\n```"}) case strings.Contains(lower, "hello") || strings.Contains(text, "你好"): list = append(list, map[string]any{"type": "reply", "of": id, "reply": "你好!我是 hidane 的主代理。"}) case kind == "recall": diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index e60f6d9..cf9bd08 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -152,6 +152,20 @@ card per row. Section labels sit flush with the card edge, `text-sm font-medium`. Every page-level empty state is an `EmptyState`. Long paths go through `PathText`, which wraps only after a slash. +### Diagrams + +A ```` ```mermaid ```` block in an agent's Markdown is drawn in place of its +code, in the same well (`bg-well`, `rounded-sm`) and with the same copy button, +which still copies the source. `src/lib/mermaid.ts` hands Mermaid the tokens, +resolved to the colors they paint: shapes are `surface-2` with `input-strong` +edges, groups `surface` with `border`, notes `accent-strong`, lines `muted`, +text `foreground` at 13px Geist, corners `rounded-sm` (groups `rounded-md`), +no gradients or shadows. The ember is kept for live state (a Gantt chart's +active work and today line), `danger` for critical work; categories (pie +slices, series, mindmap branches) are steps of `foreground`, never hues. A +wide diagram shrinks to 85% and then scrolls sideways; one Mermaid cannot draw +stays a code block. Mermaid loads with the first diagram, not with the app. + ### Status Badges label state, not category: `default` (ember) for live work or something diff --git a/frontend/e2e/README.md b/frontend/e2e/README.md index 97a544a..ffcc76a 100644 --- a/frontend/e2e/README.md +++ b/frontend/e2e/README.md @@ -3,7 +3,7 @@ Playwright drives the **real Go backend** (`bin/hidane-nogui serve`, started by `serve.mjs` on a fresh `HIDANE_HOME`) with `bin/fake/{claude,codex,pi}` standing in for the agent CLIs (`cmd/fakeagent`: real wire protocols, real guard hook, -scripted answers — a message containing `hello`/`你好` is small talk, `代码示例` gets a reply holding a code block, anything +scripted answers — a message containing `hello`/`你好` is small talk, `代码示例` gets a reply holding a code block, `流程图` one holding a Mermaid flowchart, anything else becomes a work item whose worker writes `result.txt` (`REPO=`, `FROM=`, `INPLACE` (`UNASKED`: without quoting the person), `ROUTE=` steer the Primary's repo choices); `FAKE_FAIL` fails the CLI; `FAKEAGENT_DELAY_MS` slows each turn, `FAKEAGENT_WORKER_DELAY_MS` only a worker's). Every spec runs in zh and fails on any @@ -47,7 +47,7 @@ share one server, so each uses unique text. | `schedules-status.spec.ts` | an HTTP schedule's "wake Primary" checkbox (ticked through its label text) reaches the saved spec; a prompt schedule created in the UI, run now, its run history and its answer in the conversation, deleted after an in-app confirm; Settings → Runtime status (also reached from `/status`): runtime, roles and detected CLIs | | `webhook.spec.ts` | a signed webhook is captured, triaged and answered (`rootKind: external`, visible in the UI); unsigned, wrongly signed or replayed-body deliveries are 401 and leave no event | | `cancel.spec.ts` | Stop on a running card (slow server): the running task is in the sidebar's in-progress list and its menu offers Stop; cancelling the confirm leaves it running; confirming records `execution.cancelled` before `execution.finished` with `cancelled: true`, the "执行已取消。" reply, the card shows 已中止 not 失败 | -| `shell.spec.ts` | the window shell, both flavours: ⌘, opens settings at `/settings/general`, Esc (first letting go of a field, and closing an open select list without leaving), ⌘, again and Back return to the page it was opened from; the work log's day picked from its calendar popover by keyboard, and back with 今天; the ⌘K palette (commands by keyboard, a settings section by English keyword, a task and a message from the search; Esc); ⌘N creates a work item and opens it; ⌘L focuses the composer; the confirm dialog (Cancel, Tab kept inside it, Esc with focus returned, Enter); a message's menu by right click and by ⋯ (copy text via the host clipboard in the desktop UI, no "copy link" there; hide after confirm → `message.redacted`); a code block in a reply copies its code with its 复制代码 button (shown on hover, 已复制 after; the host clipboard in the desktop UI); a task's menu (open, mark done, archive after confirm, reveal workspace in the desktop UI); ⌘B collapse survives a reload; an image dropped on the conversation is attached | +| `shell.spec.ts` | the window shell, both flavours: ⌘, opens settings at `/settings/general`, Esc (first letting go of a field, and closing an open select list without leaving), ⌘, again and Back return to the page it was opened from; the work log's day picked from its calendar popover by keyboard, and back with 今天; the ⌘K palette (commands by keyboard, a settings section by English keyword, a task and a message from the search; Esc); ⌘N creates a work item and opens it; ⌘L focuses the composer; the confirm dialog (Cancel, Tab kept inside it, Esc with focus returned, Enter); a message's menu by right click and by ⋯ (copy text via the host clipboard in the desktop UI, no "copy link" there; hide after confirm → `message.redacted`); a code block in a reply copies its code with its 复制代码 button (shown on hover, 已复制 after; the host clipboard in the desktop UI); a ```mermaid block in a reply is drawn as a diagram in its place, its source still copied by 复制代码; a task's menu (open, mark done, archive after confirm, reveal workspace in the desktop UI); ⌘B collapse survives a reload; an image dropped on the conversation is attached | | `worktrees.spec.ts` | a message naming a local repo (`REPO=` steers the fake Primary) gives the task its own worktree: the repo's `hidane.json` setup ran first, the worker wrote into the worktree and not the person's checkout, the card names repo and branch; Tasks → Worktrees (`?view=worktrees`) lists it with its uncommitted change, archiving asks, then asks again about the uncommitted change (409 until forced), removes the directory, keeps the branch and records `checkout.archived`; archived ones show with 含已归档 and open their task. A registered repo moved away is marked missing in the list, asked about in the conversation (`escalation` reason `repo_missing`, its own turn labelled 仓库检查) and can be removed (`repo.forgotten`) | | `desktop.spec.ts` | (desktop only) no token gate, sign-out or live-status, and no browser notification settings; the live stream works without the Wails runtime (SSE fallback); About opens the data directory through the host and copies its path; a task finishing in the background posts a system notification and a Dock badge, cleared when the window comes back; with both switched off the host is left alone; the webview's own context menu is suppressed except in fields; with the host endpoints 404ing, nothing surfaces as an error toast; with the host endpoints failing, a host action the person asked for (About → 在访达中显示) explains "only the desktop app can do this" in a neutral toast, not a red one | diff --git a/frontend/e2e/screenshots.mjs b/frontend/e2e/screenshots.mjs index f0f7268..15b5165 100644 --- a/frontend/e2e/screenshots.mjs +++ b/frontend/e2e/screenshots.mjs @@ -85,6 +85,7 @@ try { await api("/api/chat", { method: "POST", body: JSON.stringify({ text: "创建一个文件写上 screenshot" }) }); await api("/api/chat", { method: "POST", body: JSON.stringify({ text: "你好" }) }); await api("/api/chat", { method: "POST", body: JSON.stringify({ text: "给我一个代码示例" }) }); + await api("/api/chat", { method: "POST", body: JSON.stringify({ text: "画一个流程图" }) }); await api("/api/memories", { method: "POST", body: JSON.stringify({ kind: "preference", content: "回答保持简洁" }) }); await api("/api/policies", { method: "POST", body: JSON.stringify({ pattern: "\\bsudo\\b", reason: "no sudo" }) }); await api("/api/schedules", { diff --git a/frontend/e2e/shell.spec.ts b/frontend/e2e/shell.spec.ts index 6bff1e0..b66073e 100644 --- a/frontend/e2e/shell.spec.ts +++ b/frontend/e2e/shell.spec.ts @@ -265,6 +265,57 @@ test("a code block in a reply: its copy button copies the code and says so", asy await expect(asked.getByRole("button", { name: "复制代码" })).toBeVisible(); }); +test("a mermaid block in a reply is drawn as a diagram, its source still copied", async ({ page, api, desktop, hostCalls }) => { + await api.setAllRoles("claude"); + if (!desktop) { + await page.addInitScript(() => { + const copied: string[] = []; + (window as unknown as { __copied: string[] }).__copied = copied; + Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText: async (text: string) => void copied.push(text) } }); + }); + } + await page.goto("/"); + const asked = turn(page, await say(page, `画一个流程图 ${unique("e2e-mermaid")}`)); + const diagram = asked.locator("svg[aria-roledescription='flowchart-v2']"); + await expect(diagram).toBeVisible(); + await expect(diagram).toContainText("收到消息"); + await expect(diagram).toContainText("派给 worker"); + await expect(asked.locator("pre")).toHaveCount(0); + // Drawn in the theme's colors, not Mermaid's defaults: a node takes surface-2 and its label the foreground. + const node = diagram.locator(".node rect").first(); + const colors = await page.evaluate(() => { + const probe = document.createElement("div"); + probe.className = "bg-surface-2 text-foreground"; + document.body.append(probe); + const canvas = document.createElement("canvas").getContext("2d")!; + const srgb = (color: string) => { + canvas.fillStyle = color; + canvas.fillRect(0, 0, 1, 1); + return [...canvas.getImageData(0, 0, 1, 1).data.slice(0, 3)].join(","); + }; + const style = getComputedStyle(probe); + const tokens = { fill: srgb(style.backgroundColor), text: srgb(style.color) }; + probe.remove(); + return tokens; + }); + const drawn = await node.evaluate((rect) => getComputedStyle(rect).fill); + const label = await diagram.getByText("收到消息").evaluate((el) => getComputedStyle(el).color); + const srgb = (color: string) => color.match(/\d+/g)!.slice(0, 3).join(","); + expect(srgb(drawn)).toBe(colors.fill); + expect(srgb(label)).toBe(colors.text); + + await expect(async () => { + await diagram.hover(); + await asked.getByRole("button", { name: "复制代码" }).click({ timeout: 1_000 }); + }).toPass(); + const source = "flowchart LR\n A[收到消息] --> B{要动手吗}\n B -->|是| C[派给 worker]\n B -->|否| D[直接回复]"; + if (desktop) { + await expect.poll(() => hostCalls).toContainEqual({ path: "/api/desktop/clipboard", body: { text: source } }); + } else { + await expect.poll(() => page.evaluate(() => (window as unknown as { __copied: string[] }).__copied)).toEqual([source]); + } +}); + test("a task's menu: open, mark done, archive — and in the desktop app, show its workspace", async ({ page, api, desktop, hostCalls }) => { const title = unique("e2e-task-menu"); const { item } = await api.send<{ item: WorkItem }>("POST", "/api/work-items", { title }, 201); diff --git a/frontend/package.json b/frontend/package.json index ed7591f..43351e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "dompurify": "^3.4.15", "i18next": "^26.4.0", "marked": "^18.0.13", + "mermaid": "^12.1.0", "svelte": "^5.57.0", "tailwind-merge": "^3.6.0" }, diff --git a/frontend/src/components/Markdown.svelte b/frontend/src/components/Markdown.svelte index 17c8fa0..3596c68 100644 --- a/frontend/src/components/Markdown.svelte +++ b/frontend/src/components/Markdown.svelte @@ -2,6 +2,7 @@ import DOMPurify from "dompurify"; import { marked } from "marked"; import { mount, unmount } from "svelte"; + import { isMermaidBlock, renderMermaid } from "../lib/mermaid.js"; import { cn } from "../lib/utils.js"; import CodeCopyButton from "./CodeCopyButton.svelte"; @@ -41,10 +42,23 @@ On a touch screen the button is always shown, so the code makes room for it. */ const CODE_FRAME = "group/code relative my-2 coarse:[&>pre]:min-h-11 coarse:[&>pre]:pr-11"; + /** A drawn ```mermaid block, in the place of its code; on a touch screen it clears the copy button above it. */ + const DIAGRAM = "overflow-x-auto rounded-sm bg-well p-3 coarse:pt-11 [&>svg]:mx-auto [&>svg]:block [&>svg]:h-auto"; + function codeText(pre: HTMLPreElement): string { return ((pre.querySelector("code") ?? pre).textContent ?? "").replace(/\n$/, ""); } + /** The code stays until the diagram is drawn, and for good when Mermaid cannot draw it. */ + async function drawDiagram(pre: HTMLPreElement): Promise { + const drawn = await renderMermaid(codeText(pre)).catch(() => null); + if (!drawn) return; + const diagram = document.createElement("div"); + diagram.className = DIAGRAM; + diagram.append(drawn); + pre.replaceWith(diagram); + } + function renderMarkdown(node: HTMLDivElement): () => void { node.innerHTML = html; const buttons = [...node.querySelectorAll("pre")].map((pre) => { @@ -52,6 +66,7 @@ frame.className = CODE_FRAME; pre.replaceWith(frame); frame.append(pre); + if (isMermaidBlock(pre)) void drawDiagram(pre); return mount(CodeCopyButton, { target: frame, props: { text: codeText(pre) } }); }); return () => { diff --git a/frontend/src/lib/mermaid.ts b/frontend/src/lib/mermaid.ts new file mode 100644 index 0000000..e217eea --- /dev/null +++ b/frontend/src/lib/mermaid.ts @@ -0,0 +1,207 @@ +import DOMPurify from "dompurify"; +import type { Mermaid, MermaidConfig } from "mermaid"; + +/** Mermaid is large: it loads with the first diagram, not with the app. */ +let loading: Promise | undefined; +let rendered = 0; + +/** A fenced block marked ```mermaid. */ +export function isMermaidBlock(pre: HTMLPreElement): boolean { + return pre.querySelector("code")?.classList.contains("language-mermaid") ?? false; +} + +/** How far a wide diagram shrinks to fit (13px labels down to 11px) before it scrolls sideways instead, as a wide code block does. */ +const MIN_SCALE = 0.85; + +/** + * The diagram, sanitized, or null when Mermaid cannot draw the source (it then + * stays a code block). Agent text is untrusted: Mermaid runs at its strict + * level, and its output is sanitized again here rather than trusting that + * option name alone. + */ +export async function renderMermaid(source: string): Promise { + const mermaid = await (loading ??= load()); + if (!(await mermaid.parse(source, { suppressErrors: true }))) return null; + const id = `mermaid-${++rendered}`; + let svg: string; + try { + ({ svg } = await mermaid.render(id, source)); + } finally { + // What a failed render leaves behind in . + document.getElementById(`d${id}`)?.remove(); + } + const diagram = DOMPurify.sanitize(svg, { + RETURN_DOM_FRAGMENT: true, + USE_PROFILES: { svg: true, svgFilters: true, html: true }, + // Labels are HTML inside . + ADD_TAGS: ["foreignObject"], + HTML_INTEGRATION_POINTS: { foreignobject: true }, + }); + const root = diagram.querySelector("svg"); + const natural = Number.parseFloat(root?.style.maxWidth ?? ""); + // A Gantt chart has no width of its own: it spreads across the page it was drawn on. + if (root && natural > 0 && root.getAttribute("aria-roledescription") !== "gantt") { + root.style.minWidth = `${Math.round(natural * MIN_SCALE)}px`; + } + return diagram; +} + +async function load(): Promise { + try { + const { default: mermaid } = await import("mermaid"); + mermaid.initialize(config()); + return mermaid; + } catch (error) { + loading = undefined; + throw error; + } +} + +/** Where a diagram is drawn: a well (like a code block) inside an agent's bubble. */ +const GROUND = ["surface-2", "well"]; + +/** + * Mermaid works its palette out with khroma, which reads hex but not the + * oklch tokens: each token becomes the color it paints over the diagram's + * ground, translucent ones included. + */ +function painter(): (token: string, alpha?: number) => string { + const style = getComputedStyle(document.documentElement); + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 1; + const context = canvas.getContext("2d", { willReadFrequently: true }); + const value = (token: string) => style.getPropertyValue(`--color-${token}`).trim(); + return (token, alpha = 1) => { + if (!context) return value(token); + context.globalAlpha = 1; + for (const layer of GROUND) { + context.fillStyle = value(layer); + context.fillRect(0, 0, 1, 1); + } + context.globalAlpha = alpha; + context.fillStyle = value(token); + context.fillRect(0, 0, 1, 1); + const [r = 0, g = 0, b = 0] = context.getImageData(0, 0, 1, 1).data; + return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`; + }; +} + +/** + * The design system's dark layers in Mermaid's terms: shapes are raised + * surfaces with hairline edges, lines and arrows are muted, and the ember + * appears only for live state (a Gantt chart's active work and today). + * Categories (pie slices, chart series, mindmap branches) are steps of light, + * never hues. + */ +function config(): MermaidConfig { + const paint = painter(); + const fontFamily = getComputedStyle(document.documentElement).getPropertyValue("--font-sans").trim(); + const ground = paint("well", 0); + const shape = paint("surface-2"); + const raised = paint("accent-strong"); + const sunk = paint("surface"); + const edge = paint("input-strong"); + const hairline = paint("border"); + const line = paint("muted"); + const text = paint("foreground"); + const steps = [0.42, 0.33, 0.26, 0.2, 0.15, 0.11, 0.38, 0.29, 0.23, 0.17, 0.13, 0.09].map((alpha) => paint("foreground", alpha)); + const each = (prefix: string, value: (i: number) => string) => Object.fromEntries(steps.map((_, i) => [`${prefix}${i}`, value(i)])); + + return { + startOnLoad: false, + securityLevel: "strict", + theme: "base", + look: "classic", + fontFamily, + fontSize: 13, + // The theme has no radius; shapes take the system's, as rows and cards do. + themeCSS: ` + .node rect.label-container, rect.actor, rect.note, rect.activation0, rect.activation1, rect.activation2 { rx: var(--radius-sm); ry: var(--radius-sm); } + .cluster rect { rx: var(--radius-md); ry: var(--radius-md); } + `, + flowchart: { padding: 12, nodeSpacing: 32, rankSpacing: 40, diagramPadding: 4 }, + sequence: { diagramMarginX: 4, diagramMarginY: 4, height: 40 }, + themeVariables: { + darkMode: true, + fontFamily, + fontSize: "13px", + useGradient: false, + dropShadow: "none", + background: ground, + primaryColor: shape, + primaryTextColor: text, + primaryBorderColor: edge, + secondaryColor: raised, + secondaryTextColor: text, + secondaryBorderColor: edge, + tertiaryColor: sunk, + tertiaryTextColor: text, + tertiaryBorderColor: hairline, + mainBkg: shape, + nodeBorder: edge, + clusterBkg: sunk, + clusterBorder: hairline, + lineColor: line, + textColor: text, + titleColor: text, + edgeLabelBackground: ground, + noteBkgColor: raised, + noteTextColor: text, + noteBorderColor: edge, + actorBkg: shape, + actorBorder: edge, + actorTextColor: text, + actorLineColor: paint("input"), + signalColor: line, + signalTextColor: text, + labelBoxBkgColor: shape, + labelBoxBorderColor: edge, + labelTextColor: text, + loopTextColor: text, + activationBkgColor: raised, + activationBorderColor: edge, + sequenceNumberColor: ground, + rowOdd: shape, + rowEven: sunk, + attributeBackgroundColorOdd: shape, + attributeBackgroundColorEven: sunk, + taskBkgColor: shape, + taskBorderColor: edge, + taskTextColor: text, + taskTextLightColor: text, + taskTextDarkColor: text, + taskTextOutsideColor: text, + activeTaskBkgColor: paint("primary", 0.22), + activeTaskBorderColor: paint("primary"), + doneTaskBkgColor: sunk, + doneTaskBorderColor: hairline, + critBkgColor: paint("danger", 0.22), + critBorderColor: paint("danger"), + todayLineColor: paint("primary"), + gridColor: hairline, + sectionBkgColor: paint("accent"), + sectionBkgColor2: sunk, + altSectionBkgColor: ground, + excludeBkgColor: sunk, + errorBkgColor: paint("danger", 0.22), + errorTextColor: text, + ...each("cScale", (i) => steps[i]!), + ...each("cScaleLabel", () => text), + ...each("cScaleInv", (i) => steps[i]!), + ...Object.fromEntries(steps.map((step, i) => [`pie${i + 1}`, step])), + pieOpacity: "1", + pieStrokeColor: ground, + pieStrokeWidth: "1.5px", + pieOuterStrokeColor: hairline, + pieOuterStrokeWidth: "1px", + pieTitleTextSize: "14px", + pieTitleTextColor: text, + pieSectionTextSize: "12px", + pieSectionTextColor: text, + pieLegendTextSize: "12px", + pieLegendTextColor: text, + xyChart: { plotColorPalette: steps.slice(0, 6).join(",") }, + radar: { graticuleColor: edge }, + }, + }; +} diff --git a/frontend/test/markdown.test.ts b/frontend/test/markdown.test.ts index 42b82d4..db7069c 100644 --- a/frontend/test/markdown.test.ts +++ b/frontend/test/markdown.test.ts @@ -6,6 +6,21 @@ import Markdown from "../src/components/Markdown.svelte"; import i18n from "../src/i18n/index.js"; import { clearToasts, toastStore } from "../src/lib/toast.js"; +// jsdom cannot lay out a diagram: Mermaid is replaced by a stand-in that "draws" what the real one could +// return from hostile input, so these tests cover what hidane does with its output. +const mermaid = vi.hoisted(() => ({ + initialize: vi.fn(), + parse: vi.fn(async (source: string) => (source.includes("broken") ? false : { diagramType: "flowchart-v2" })), + render: vi.fn(async (id: string, source: string) => ({ + svg: + `` + + `` + + `

${source.split("\n").at(-1)?.trim()}

` + + `
`, + })), +})); +vi.mock("mermaid", () => ({ default: mermaid })); + describe("Markdown", () => { it("renders GFM while stripping executable HTML attributes", () => { const { container } = render(Markdown, { @@ -77,4 +92,47 @@ describe("Markdown", () => { expect(screen.queryByRole("button", { name: "已复制" })).toBeNull(); }); }); + + describe("mermaid blocks", () => { + beforeEach(async () => { + await i18n.changeLanguage("zh"); + // jsdom has no canvas; the theme then takes the tokens as written. + vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(null); + }); + + it("draws a diagram in place of its code, sanitized, keeping the source to copy", async () => { + const writeText = vi.fn<(text: string) => Promise>(async () => undefined); + Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText } }); + const source = "flowchart LR\n A --> B"; + const { container } = render(Markdown, { props: { content: `Plan:\n\n\`\`\`mermaid\n${source}\n\`\`\`\n\n\`\`\`sh\nmake test\n\`\`\`` } }); + + const svg = await vi.waitFor(() => { + const drawn = container.querySelector("svg[aria-roledescription]"); + expect(drawn).not.toBeNull(); + return drawn!; + }); + expect(svg.textContent).toContain("A --> B"); + expect(container.querySelectorAll("pre")).toHaveLength(1); + expect(container.querySelector("pre")?.textContent).toContain("make test"); + // Strict level asked of Mermaid, and nothing executable let through regardless. + expect(mermaid.initialize).toHaveBeenCalledWith(expect.objectContaining({ securityLevel: "strict", theme: "base", startOnLoad: false })); + expect(container.querySelector("script, [onload], [onerror]")).toBeNull(); + expect(container.querySelector("a")?.getAttribute("href") ?? "").not.toContain("javascript:"); + // Shrinks to fit only so far, then scrolls. + expect(svg.style.minWidth).toBe("340px"); + + const buttons = screen.getAllByRole("button", { name: "复制代码" }); + expect(buttons).toHaveLength(2); + await fireEvent.click(buttons[0]!); + expect(writeText).toHaveBeenCalledWith(source); + }); + + it("leaves a block Mermaid cannot draw as code", async () => { + const { container } = render(Markdown, { props: { content: "```mermaid\nflowchart broken ((\n```" } }); + await vi.waitFor(() => expect(mermaid.parse).toHaveBeenCalledWith("flowchart broken ((", { suppressErrors: true })); + await Promise.resolve(); + expect(container.querySelector("svg[aria-roledescription]")).toBeNull(); + expect(container.querySelector("pre code.language-mermaid")?.textContent).toContain("flowchart broken (("); + }); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee739f7..84f97b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: marked: specifier: ^18.0.13 version: 18.0.13 + mermaid: + specifier: ^12.1.0 + version: 12.1.0 svelte: specifier: ^5.57.0 version: 5.57.0 @@ -89,6 +92,9 @@ packages: '@adobe/css-tools@4.5.0': resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@antfu/install-pkg@2.1.0': + resolution: {integrity: sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==} + '@asamuzakjp/css-color@6.0.7': resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} engines: {node: ^22.13.0 || >=24.0.0} @@ -109,10 +115,28 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true + '@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==} + '@csstools/color-helpers@6.1.1': resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} engines: {node: '>=20.19.0'} @@ -329,6 +353,12 @@ packages: '@fontsource-variable/geist@5.3.0': resolution: {integrity: sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==} + '@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==} + '@internationalized/date@3.12.4': resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==} @@ -353,6 +383,10 @@ packages: peerDependencies: svelte: ^5 + '@mermaid-js/parser@2.0.1': + resolution: {integrity: sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==} + engines: {node: '>=22.12.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -627,18 +661,117 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.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==} '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/node@26.2.0': resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vitest/expect@4.1.11': resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} @@ -714,6 +847,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chevrotain@13.2.0: + resolution: {integrity: sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA==} + engines: {node: '>=22.0.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -725,9 +862,23 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 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'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -735,10 +886,169 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + 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-urls@7.0.0: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -746,6 +1056,9 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -766,6 +1079,9 @@ packages: dompurify@3.4.15: resolution: {integrity: sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==} + elkjs@0.9.3: + resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} + enhanced-resolve@5.24.5: resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} @@ -777,6 +1093,9 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + 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'} @@ -817,6 +1136,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -829,6 +1151,13 @@ packages: typescript: optional: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -836,6 +1165,13 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -858,6 +1194,19 @@ packages: canvas: optional: true + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1009,6 +1358,9 @@ packages: locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -1023,6 +1375,11 @@ packages: magic-string@1.3.1: resolution: {integrity: sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + marked@18.0.13: resolution: {integrity: sha512-xTxVzZsBFwunP6HDmtBkabUQEYArnP7/rMDGmPj9SlrKlQ4i8MdYVow+nJL0eOqwpUqhzBoTBRADGN6uYwPyOw==} engines: {node: '>= 20'} @@ -1031,6 +1388,10 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mermaid@12.1.0: + resolution: {integrity: sha512-wlVCp+8eTupfCeeFvoZNNiTuHrvag0P2jz/ILgb/f/6jkVokUefOcujefi8qUe/j2asHiSePncVsz/xzzA80LQ==} + engines: {node: '>=22.12.0'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1048,9 +1409,15 @@ packages: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1071,6 +1438,12 @@ packages: engines: {node: '>=20'} hasBin: true + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss@8.5.26: resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} @@ -1098,11 +1471,17 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown@1.2.5: resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + runed@0.35.1: resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==} peerDependencies: @@ -1112,10 +1491,16 @@ packages: '@sveltejs/kit': optional: true + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -1140,6 +1525,9 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + svelte-check@4.7.6: resolution: {integrity: sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==} engines: {node: '>= 18.0.0'} @@ -1181,6 +1569,10 @@ packages: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -1204,6 +1596,10 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1224,6 +1620,10 @@ packages: resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} engines: {node: '>=22.19.0'} + uuid@14.0.2: + resolution: {integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==} + hasBin: true + vite@8.2.2: resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1360,6 +1760,11 @@ snapshots: '@adobe/css-tools@4.5.0': {} + '@antfu/install-pkg@2.1.0': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.1 + '@asamuzakjp/css-color@6.0.7': dependencies: '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -1385,10 +1790,27 @@ snapshots: '@babel/runtime@7.29.7': {} + '@braintree/sanitize-url@7.1.2': {} + '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 + '@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': {} + '@csstools/color-helpers@6.1.1': {} '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -1508,6 +1930,14 @@ snapshots: '@fontsource-variable/geist@5.3.0': {} + '@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 + '@internationalized/date@3.12.4': dependencies: '@swc/helpers': 0.5.23 @@ -1535,6 +1965,10 @@ snapshots: dependencies: svelte: 5.57.0 + '@mermaid-js/parser@2.0.1': + dependencies: + '@chevrotain/types': 13.2.0 + '@opentelemetry/api@1.9.0': optional: true @@ -1730,10 +2164,129 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.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/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + '@types/node@26.2.0': dependencies: undici-types: 8.3.0 @@ -1742,6 +2295,11 @@ snapshots: '@types/trusted-types@2.0.7': optional: true + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 @@ -1820,6 +2378,14 @@ snapshots: chai@6.2.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@4.0.3: dependencies: readdirp: 4.1.2 @@ -1830,8 +2396,20 @@ snapshots: clsx@2.1.1: {} + commander@7.2.0: {} + + commander@8.3.0: {} + convert-source-map@2.0.0: {} + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -1839,6 +2417,190 @@ snapshots: css.escape@1.5.1: {} + 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-urls@7.0.0: dependencies: whatwg-mimetype: 5.0.0 @@ -1846,10 +2608,16 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + dayjs@1.11.23: {} + decimal.js@10.6.0: {} deepmerge@4.3.1: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -1864,6 +2632,8 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + elkjs@0.9.3: {} + enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 @@ -1873,6 +2643,8 @@ snapshots: es-module-lexer@2.3.2: {} + es-toolkit@1.52.0: {} + esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -1924,6 +2696,8 @@ snapshots: graceful-fs@4.2.11: {} + hachure-fill@0.5.2: {} + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.1 @@ -1934,10 +2708,20 @@ snapshots: optionalDependencies: typescript: 6.0.3 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + import-meta-resolve@4.2.0: {} + indent-string@4.0.0: {} inline-style-parser@0.2.7: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-potential-custom-element-name@1.0.1: {} is-reference@3.0.3: @@ -1974,6 +2758,16 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + katex@0.16.47: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -2074,6 +2868,8 @@ snapshots: locate-character@3.0.0: {} + lodash-es@4.18.1: {} + lru-cache@11.5.2: {} lz-string@1.5.0: {} @@ -2086,10 +2882,38 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 + marked@16.4.2: {} + marked@18.0.13: {} mdn-data@2.27.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.15 + 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.2 + min-indent@1.0.1: {} mri@1.2.0: {} @@ -2098,10 +2922,14 @@ snapshots: obug@2.1.4: {} + package-manager-detector@1.8.0: {} + parse5@8.0.1: dependencies: entities: 8.0.0 + path-data-parser@0.1.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2114,6 +2942,13 @@ snapshots: dependencies: playwright-core: 1.63.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 + postcss@8.5.26: dependencies: nanoid: 3.3.18 @@ -2139,6 +2974,8 @@ snapshots: require-from-string@2.0.2: {} + robust-predicates@3.0.3: {} + rolldown@1.2.5: dependencies: '@oxc-project/types': 0.146.0 @@ -2160,6 +2997,13 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.5 '@rolldown/binding-win32-x64-msvc': 1.2.5 + 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 + runed@0.35.1(svelte@5.57.0): dependencies: dequal: 2.0.3 @@ -2167,10 +3011,14 @@ snapshots: lz-string: 1.5.0 svelte: 5.57.0 + rw@1.3.3: {} + sade@1.8.1: dependencies: mri: 1.2.0 + safer-buffer@2.1.2: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -2191,6 +3039,8 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + stylis@4.4.0: {} + svelte-check@4.7.6(picomatch@4.0.5)(svelte@5.57.0)(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -2247,6 +3097,8 @@ snapshots: tinyexec@1.3.0: {} + tinyexec@1.3.1: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) @@ -2268,6 +3120,8 @@ snapshots: dependencies: punycode: 2.3.1 + ts-dedent@2.3.0: {} + tslib@2.8.1: {} tsx@4.23.12: @@ -2284,6 +3138,8 @@ snapshots: undici@8.10.2: {} + uuid@14.0.2: {} + vite@8.2.2(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0