diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 39a8451d8..2739289e4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,8 @@ "plugins": [ { "name": "understand-anything", - "source": "./understand-anything-plugin" + "source": "./understand-anything-plugin", + "version": "2.4.0" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 071856991..a28ed0b6f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.3.1", + "version": "2.4.0", "author": { "name": "Lum1104" }, diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 1da213dc0..9d814fa6b 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "understand-anything", "displayName": "Understand Anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.3.1", + "version": "2.4.0", "author": { "name": "Lum1104" }, diff --git a/.gitignore b/.gitignore index 4a966c1d0..8b4a7a4a9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ coverage/ *.log .claude/ .worktrees/ +.superpowers/ homepage/public/demo/ diff --git a/docs/superpowers/plans/2026-04-20-mermaid-service-flowchart.md b/docs/superpowers/plans/2026-04-20-mermaid-service-flowchart.md new file mode 100644 index 000000000..cabd595ef --- /dev/null +++ b/docs/superpowers/plans/2026-04-20-mermaid-service-flowchart.md @@ -0,0 +1,2009 @@ +# Mermaid Service Flowchart Popup — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** When a user clicks a `domain` node in the Domain graph, render a 480×260 slide-up popup showing a Mermaid flowchart of that domain's internal business logic, with a fullscreen modal for enlargement. Source is generated offline by the agent pipeline and persisted in the knowledge graph. + +**Architecture:** Schema extension on `DomainMeta.mermaid` (optional string). Agent pipeline (`domain-analyzer` + `assemble-reviewer`) generates and validates the source. Dashboard lazily loads `mermaid` npm package on first popup open, renders via a shared `MermaidRenderer` component, with a 5-candidate collision-avoiding position hook for the popup. Fullscreen modal uses React Portal. No runtime LLM calls. + +**Tech Stack:** TypeScript 5, React 19, Zustand 5, `mermaid` 10.x (lazy), Vitest 3, `@testing-library/react`, `jsdom`. Package layout: `packages/core` (schema), `packages/dashboard` (UI), `understand-anything-plugin/agents/*.md` (agent prompts), `understand-anything-plugin/scripts/*.mjs` (validation utilities). + +**Reference spec:** [`docs/superpowers/specs/2026-04-20-mermaid-service-flowchart-design.md`](../specs/2026-04-20-mermaid-service-flowchart-design.md) + +--- + +## File Structure + +**Create:** +- `understand-anything-plugin/scripts/validate-mermaid.mjs` — Node CLI for dry-run Mermaid parsing +- `understand-anything-plugin/packages/dashboard/vitest.config.ts` — test runner config +- `understand-anything-plugin/packages/dashboard/src/test/setup.ts` — JSDOM test setup +- `understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts` — CSS-vars → Mermaid themeVariables +- `understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx` — shared source→SVG renderer +- `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx` — 480×260 slide-up +- `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx` — fullscreen modal +- `understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts` — 5-candidate collision hook +- `understand-anything-plugin/packages/dashboard/src/components/__tests__/*.test.tsx` — component tests +- `understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.test.ts` — hook test +- `understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts` — schema contract test + +**Modify:** +- `understand-anything-plugin/packages/core/src/types.ts` — add `mermaid?: string` to `DomainMeta` +- `understand-anything-plugin/packages/core/src/schema.ts` — add `mermaid` to `DomainMetaSchema` +- `understand-anything-plugin/packages/dashboard/package.json` — add deps + test script +- `understand-anything-plugin/packages/dashboard/src/store.ts` — add `MermaidPopupSlice` +- `understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts` — emit change event +- `understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx` — trigger popup on click +- `understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx` — mount popup + modal +- `understand-anything-plugin/agents/domain-analyzer.md` — Mermaid generation rules +- `understand-anything-plugin/agents/assemble-reviewer.md` — dry-run validation step + +--- + +## Pre-flight Check + +Before starting, run these commands from the project root and record the output. + +- [ ] **Run:** `cd understand-anything-plugin && pnpm --filter @understand-anything/core test` — Expect: all passing, establishes baseline +- [ ] **Run:** `cd understand-anything-plugin && pnpm --filter @understand-anything/core build` — Expect: success +- [ ] **Run:** `git status --short` — Expect: clean working tree (or only the spec from brainstorming) + +--- + +## Phase A: Core Schema Extension + +### Task 1: Add `DomainMeta.mermaid` field to types and schema + +**Files:** +- Create: `understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts` +- Modify: `understand-anything-plugin/packages/core/src/types.ts` (interface `DomainMeta`, around line 30–50) +- Modify: `understand-anything-plugin/packages/core/src/schema.ts:353-359` (`DomainMetaSchema`) + +- [ ] **Step 1: Write failing schema test** + +Create `understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts`: + +```typescript +import { describe, it, expect } from "vitest"; +import { validateGraph } from "../schema.js"; + +function graphWith(domainMeta: Record) { + return { + nodes: [ + { + id: "d1", + type: "domain", + name: "Checkout", + summary: "checkout domain", + tags: [], + complexity: "simple", + domainMeta, + }, + ], + edges: [], + }; +} + +describe("DomainMeta.mermaid schema", () => { + it("accepts a valid mermaid source string", () => { + const result = validateGraph( + graphWith({ mermaid: "flowchart TD\n A --> B" }), + ); + expect(result.success).toBe(true); + }); + + it("accepts a graph with no mermaid field", () => { + const result = validateGraph(graphWith({ entities: ["Order"] })); + expect(result.success).toBe(true); + }); + + it("rejects an empty mermaid string", () => { + const result = validateGraph(graphWith({ mermaid: "" })); + expect(result.success).toBe(false); + }); + + it("rejects a non-string mermaid value", () => { + const result = validateGraph(graphWith({ mermaid: 42 as unknown as string })); + expect(result.success).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Run the test, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- schema.mermaid` +Expected: FAIL on "rejects an empty mermaid string" (current schema has no such field, so empty string passes via passthrough). + +- [ ] **Step 3: Update TypeScript interface** + +Open `understand-anything-plugin/packages/core/src/types.ts` and locate the `DomainMeta` interface. Add the `mermaid` field: + +```typescript +export interface DomainMeta { + entities?: string[]; + businessRules?: string[]; + crossDomainInteractions?: string[]; + entryPoint?: string; + entryType?: "http" | "cli" | "event" | "cron" | "manual"; + mermaid?: string; +} +``` + +- [ ] **Step 4: Update Zod schema** + +Open `understand-anything-plugin/packages/core/src/schema.ts:353-359` and add the `mermaid` field to `DomainMetaSchema`: + +```typescript +const DomainMetaSchema = z.object({ + entities: z.array(z.string()).optional(), + businessRules: z.array(z.string()).optional(), + crossDomainInteractions: z.array(z.string()).optional(), + entryPoint: z.string().optional(), + entryType: z.enum(["http", "cli", "event", "cron", "manual"]).optional(), + mermaid: z.string().min(1).optional(), +}).passthrough(); +``` + +- [ ] **Step 5: Run tests, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- schema.mermaid` +Expected: PASS all 4 cases. + +- [ ] **Step 6: Run the full core test suite, confirm no regressions** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test` +Expected: all passing. + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/types.ts \ + understand-anything-plugin/packages/core/src/schema.ts \ + understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts +git commit -m "feat(core): add optional DomainMeta.mermaid field to schema" +``` + +--- + +### Task 2: Verify `sanitizeGraph` preserves `mermaid` field + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts` + +Context: The spec says `sanitizeGraph()` / `autoFixGraph()` must pass through the new field. Verify by test (no implementation change expected because of `.passthrough()`). + +- [ ] **Step 1: Add a preservation test case** + +Append to `schema.mermaid.test.ts`: + +```typescript +import { sanitizeGraph } from "../schema.js"; + +describe("sanitizeGraph preserves mermaid", () => { + it("keeps a valid mermaid source through sanitization", () => { + const g = graphWith({ mermaid: "flowchart TD\n A --> B" }); + const cleaned = sanitizeGraph(g as never); + expect(cleaned.nodes[0].domainMeta?.mermaid).toBe("flowchart TD\n A --> B"); + }); +}); +``` + +- [ ] **Step 2: Run the test** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- schema.mermaid` +Expected: PASS (no code change needed; `.passthrough()` preserves the field). + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts +git commit -m "test(core): verify sanitizeGraph preserves DomainMeta.mermaid" +``` + +--- + +## Phase B: Mermaid Validation Utility + +### Task 3: Create `validate-mermaid.mjs` CLI + +**Files:** +- Create: `understand-anything-plugin/scripts/validate-mermaid.mjs` + +Context: Used by the `assemble-reviewer` agent to dry-run parse Mermaid sources before writing them to `knowledge-graph.json`. Reads JSON from stdin (`{ "id": "...", "source": "..." }` per line) and writes JSON to stdout (`{ "id": "...", "ok": true | false, "error": "..." }`). + +- [ ] **Step 1: Add mermaid as a plugin-level devDependency** + +Open `understand-anything-plugin/package.json` and add to `devDependencies` (or `dependencies` if devDependencies missing): + +```json +"mermaid": "^10.9.0" +``` + +- [ ] **Step 2: Install the dependency** + +Run: `cd understand-anything-plugin && pnpm install` +Expected: lockfile updated, `node_modules/mermaid` populated. + +- [ ] **Step 3: Write the script** + +Create `understand-anything-plugin/scripts/validate-mermaid.mjs`: + +```javascript +#!/usr/bin/env node +// Reads JSONL on stdin: { "id": string, "source": string } per line. +// Writes JSONL on stdout: { "id": string, "ok": boolean, "error"?: string } per line. +// Exit code 0 regardless of parse failures — the caller inspects the output lines. + +import { createInterface } from "node:readline"; +import mermaid from "mermaid"; + +mermaid.initialize({ startOnLoad: false }); + +const rl = createInterface({ input: process.stdin, crlfDelay: Infinity }); + +for await (const line of rl) { + const trimmed = line.trim(); + if (!trimmed) continue; + let payload; + try { + payload = JSON.parse(trimmed); + } catch (err) { + process.stdout.write( + JSON.stringify({ id: null, ok: false, error: `invalid-json: ${err.message}` }) + "\n", + ); + continue; + } + const { id, source } = payload; + try { + await mermaid.parse(source); + process.stdout.write(JSON.stringify({ id, ok: true }) + "\n"); + } catch (err) { + process.stdout.write( + JSON.stringify({ id, ok: false, error: String(err?.message ?? err) }) + "\n", + ); + } +} +``` + +Then make executable: `chmod +x understand-anything-plugin/scripts/validate-mermaid.mjs`. + +- [ ] **Step 4: Manually test with a valid sample** + +Run from project root: + +```bash +echo '{"id":"d1","source":"flowchart TD\n A --> B"}' | node understand-anything-plugin/scripts/validate-mermaid.mjs +``` + +Expected output: `{"id":"d1","ok":true}` + +- [ ] **Step 5: Manually test with an invalid sample** + +Run: + +```bash +echo '{"id":"d2","source":"flowchart TD\n A --> $$invalid"}' | node understand-anything-plugin/scripts/validate-mermaid.mjs +``` + +Expected output: `{"id":"d2","ok":false,"error":"..."}` (`ok` is `false`, `error` contains a parse message). + +- [ ] **Step 6: Commit** + +```bash +git add understand-anything-plugin/scripts/validate-mermaid.mjs \ + understand-anything-plugin/package.json \ + understand-anything-plugin/pnpm-lock.yaml +git commit -m "feat(plugin): add validate-mermaid.mjs dry-run parser script" +``` + +--- + +## Phase C: Agent Prompt Changes + +### Task 4: Extend `domain-analyzer.md` with Mermaid generation rules + +**Files:** +- Modify: `understand-anything-plugin/agents/domain-analyzer.md` + +- [ ] **Step 1: Read the current file structure** + +Open `understand-anything-plugin/agents/domain-analyzer.md` and identify: +- The section describing the intermediate JSON output shape (listing fields on `domain` nodes) +- A natural insertion point after the `domainMeta` description + +- [ ] **Step 2: Append Mermaid generation rules to the agent prompt** + +Insert a new section titled `### Mermaid flowchart generation` (placement: immediately after the `domainMeta` description; if no such section exists, place at the end of the "Output" / "Node types" section): + +```markdown +### Mermaid flowchart generation + +For each `domain` node you produce, also emit `domainMeta.mermaid` — a Mermaid `flowchart TD` source string representing the internal business logic of that domain. Rules: + +- Each logical step or component in the domain is a flowchart node (e.g. "Cart Retrieval", "Currency Convert", "Payment Charge"). +- Edges represent control or data flow between steps. +- **When an edge corresponds to an API call, label the arrow with the API** using `|METHOD /path|` syntax (e.g. `A -->|POST /charge| B`). +- Keep node labels concise (≤40 characters); use `[...]` for process nodes and `{...}` for decisions. +- Target 5–15 nodes per domain; if more, group related steps into Mermaid subgraphs. +- Output valid Mermaid syntax — it will be dry-run parsed by `scripts/validate-mermaid.mjs` before commit; invalid sources will be stripped. + +Example: + +\`\`\` +flowchart TD + A[Cart Retrieval] -->|GET /cart| B[Product Lookup] + B -->|GET /products/:id| C[Currency Convert] + C -->|POST /currency/convert| D[Shipping Quote] + D -->|POST /shipping/quote| E[Payment Charge] + E -->|POST /charge| F[Email Confirmation] +\`\`\` + +Emit the source as a JSON string (newlines escaped as `\n`). Place under the domain node's `domainMeta.mermaid`. Do NOT emit for `flow` or `step` nodes — only for `domain` nodes. +``` + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/agents/domain-analyzer.md +git commit -m "feat(agents): instruct domain-analyzer to emit Mermaid flowcharts" +``` + +--- + +### Task 5: Extend `assemble-reviewer.md` with Mermaid dry-run step + +**Files:** +- Modify: `understand-anything-plugin/agents/assemble-reviewer.md` + +- [ ] **Step 1: Add a validation step** + +Open `understand-anything-plugin/agents/assemble-reviewer.md` and append (after the existing schema validation step) a new step: + +```markdown +### Step: Mermaid dry-run validation + +After schema validation, for every node in the assembled graph with a non-empty `domainMeta.mermaid` field, dry-run-parse the source to ensure it is valid Mermaid syntax. + +Implementation: + +1. Collect all nodes with `domainMeta.mermaid` into a JSONL stream where each line is `{"id": "", "source": ""}`. +2. Pipe the stream to `node understand-anything-plugin/scripts/validate-mermaid.mjs`. +3. Read the JSONL output. For every line where `ok === false`: + - Remove the `mermaid` field from that node in the assembled graph (do NOT abort the whole assembly). + - Append a log line `[mermaid-invalid] : ` to `.understand-anything/intermediate/mermaid-errors.log`. +4. Continue with remaining reviewer steps. + +Example Bash invocation: + +\`\`\`bash +jq -c '.nodes[] | select(.domainMeta.mermaid != null) | {id: .id, source: .domainMeta.mermaid}' \ + .understand-anything/intermediate/assembled-graph.json \ + | node understand-anything-plugin/scripts/validate-mermaid.mjs \ + > .understand-anything/intermediate/mermaid-validation.jsonl +\`\`\` + +Then process `mermaid-validation.jsonl` with a follow-up jq/node step to strip invalid entries and produce `mermaid-errors.log`. +``` + +- [ ] **Step 2: Commit** + +```bash +git add understand-anything-plugin/agents/assemble-reviewer.md +git commit -m "feat(agents): assemble-reviewer dry-run validates Mermaid sources" +``` + +--- + +## Phase D: Dashboard Test Infrastructure + Mermaid Dependency + +### Task 6: Add Vitest + testing-library to dashboard + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/package.json` +- Create: `understand-anything-plugin/packages/dashboard/vitest.config.ts` +- Create: `understand-anything-plugin/packages/dashboard/src/test/setup.ts` + +Context: The dashboard currently has no test runner. We add Vitest + JSDOM + `@testing-library/react` for component and hook tests. + +- [ ] **Step 1: Add dev dependencies to `packages/dashboard/package.json`** + +Add to `devDependencies`: + +```json +"vitest": "^3.1.0", +"jsdom": "^25.0.0", +"@testing-library/react": "^16.1.0", +"@testing-library/jest-dom": "^6.6.0", +"@testing-library/user-event": "^14.5.0", +"@vitest/ui": "^3.1.0", +"@vitejs/plugin-react": "^4.3.0" +``` + +Add to `dependencies`: + +```json +"mermaid": "^10.9.0" +``` + +Add to `scripts`: + +```json +"test": "vitest run", +"test:watch": "vitest" +``` + +- [ ] **Step 2: Install** + +Run: `cd understand-anything-plugin && pnpm install` +Expected: lockfile updated. + +- [ ] **Step 3: Create Vitest config** + +Create `understand-anything-plugin/packages/dashboard/vitest.config.ts`: + +```typescript +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + setupFiles: ["./src/test/setup.ts"], + include: ["src/**/*.test.{ts,tsx}"], + }, +}); +``` + +- [ ] **Step 4: Create test setup file** + +Create `understand-anything-plugin/packages/dashboard/src/test/setup.ts`: + +```typescript +import "@testing-library/jest-dom/vitest"; +import { afterEach } from "vitest"; +import { cleanup } from "@testing-library/react"; + +afterEach(() => { + cleanup(); +}); + +if (!("matchMedia" in window)) { + Object.defineProperty(window, "matchMedia", { + value: (query: string) => ({ + matches: false, + media: query, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + onchange: null, + }), + }); +} + +if (!("randomUUID" in crypto)) { + Object.defineProperty(crypto, "randomUUID", { + value: () => + "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }), + }); +} +``` + +- [ ] **Step 5: Smoke test the setup** + +Create `understand-anything-plugin/packages/dashboard/src/test/smoke.test.tsx`: + +```typescript +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; + +describe("test infrastructure smoke", () => { + it("renders and queries the DOM", () => { + render(
hello world
); + expect(screen.getByText("hello world")).toBeInTheDocument(); + }); +}); +``` + +- [ ] **Step 6: Run tests** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test` +Expected: 1 test, PASS. + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/package.json \ + understand-anything-plugin/packages/dashboard/vitest.config.ts \ + understand-anything-plugin/packages/dashboard/src/test/setup.ts \ + understand-anything-plugin/packages/dashboard/src/test/smoke.test.tsx \ + understand-anything-plugin/pnpm-lock.yaml +git commit -m "chore(dashboard): add Vitest + testing-library test infrastructure" +``` + +--- + +## Phase E: Theme Engine Event Emitter + +### Task 7: Add `onThemeChange` subscription to theme-engine + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts` + +Context: Mermaid SVGs embed theme colors at render time. When the user switches themes, we need to re-render. Current `applyTheme()` only mutates CSS variables without notifying listeners. + +- [ ] **Step 1: Write a failing test** + +Create `understand-anything-plugin/packages/dashboard/src/themes/__tests__/theme-engine.test.ts`: + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { applyTheme, onThemeChange } from "../theme-engine.js"; + +describe("theme-engine change subscribers", () => { + it("calls registered listener after applyTheme", () => { + const listener = vi.fn(); + const unsubscribe = onThemeChange(listener); + applyTheme({ presetId: "dark-luxury", accentId: "gold" }); + expect(listener).toHaveBeenCalledTimes(1); + unsubscribe(); + applyTheme({ presetId: "dark-luxury", accentId: "gold" }); + expect(listener).toHaveBeenCalledTimes(1); + }); +}); +``` + +Note: preset/accent IDs must match whatever is already defined in `presets.ts`. If `"dark-luxury"` / `"gold"` don't match, replace with real IDs found there. + +- [ ] **Step 2: Run the test, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- theme-engine` +Expected: FAIL — `onThemeChange is not a function`. + +- [ ] **Step 3: Extend theme-engine with a subscription list** + +Open `understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts`. Before `applyTheme`, add a listener set and export an `onThemeChange` function: + +```typescript +type ThemeChangeListener = () => void; +const themeChangeListeners = new Set(); + +export function onThemeChange(listener: ThemeChangeListener): () => void { + themeChangeListeners.add(listener); + return () => themeChangeListeners.delete(listener); +} +``` + +At the end of the existing `applyTheme(config)` function body, append: + +```typescript +for (const listener of themeChangeListeners) { + try { + listener(); + } catch (err) { + console.error("[theme-engine] listener error:", err); + } +} +``` + +- [ ] **Step 4: Run the test, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- theme-engine` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts \ + understand-anything-plugin/packages/dashboard/src/themes/__tests__/theme-engine.test.ts +git commit -m "feat(dashboard): emit onThemeChange notifications from theme-engine" +``` + +--- + +### Task 8: Create `mermaid-theme.ts` helper + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts` +- Create: `understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts` + +- [ ] **Step 1: Write a failing test** + +Create `understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts`: + +```typescript +import { describe, it, expect, beforeEach } from "vitest"; +import { getMermaidTheme } from "../mermaid-theme.js"; + +describe("getMermaidTheme", () => { + beforeEach(() => { + const s = document.documentElement.style; + s.setProperty("--color-elevated", "#121212"); + s.setProperty("--color-accent", "#d4a574"); + s.setProperty("--color-accent-dim", "#a88554"); + s.setProperty("--color-text-primary", "#f0e6d2"); + s.setProperty("--color-surface", "#161616"); + s.setProperty("--color-root", "#0a0a0a"); + s.setProperty("--font-sans", "'Inter', system-ui, sans-serif"); + }); + + it("reads CSS variables into Mermaid themeVariables", () => { + const theme = getMermaidTheme(); + expect(theme.background).toBe("#121212"); + expect(theme.primaryBorderColor).toBe("#d4a574"); + expect(theme.primaryColor).toBe("#a88554"); + expect(theme.fontFamily).toContain("Inter"); + }); +}); +``` + +- [ ] **Step 2: Run the test, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- mermaid-theme` +Expected: FAIL — module not found. + +- [ ] **Step 3: Implement the helper** + +Create `understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts`: + +```typescript +export interface MermaidThemeVariables { + background: string; + primaryColor: string; + primaryBorderColor: string; + primaryTextColor: string; + lineColor: string; + secondaryColor: string; + tertiaryColor: string; + edgeLabelBackground: string; + mainBkg: string; + fontFamily: string; + fontSize: string; +} + +export function getMermaidTheme(): MermaidThemeVariables { + const style = getComputedStyle(document.documentElement); + const read = (v: string) => style.getPropertyValue(v).trim(); + return { + background: read("--color-elevated"), + primaryColor: read("--color-accent-dim"), + primaryBorderColor: read("--color-accent"), + primaryTextColor: read("--color-text-primary"), + lineColor: read("--color-accent-dim"), + secondaryColor: read("--color-surface"), + tertiaryColor: read("--color-root"), + edgeLabelBackground: read("--color-elevated"), + mainBkg: read("--color-elevated"), + fontFamily: read("--font-sans"), + fontSize: "14px", + }; +} +``` + +- [ ] **Step 4: Run the test, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- mermaid-theme` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts \ + understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts +git commit -m "feat(dashboard): add mermaid-theme helper mapping CSS vars to Mermaid vars" +``` + +--- + +## Phase F: Store Extension + +### Task 9: Add `MermaidPopupSlice` to the dashboard store + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/store.ts` + +Context: Four new state fields + four actions, mirroring the existing `codeViewer` slice. + +- [ ] **Step 1: Write a failing test** + +Create `understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts`: + +```typescript +import { describe, it, expect, beforeEach } from "vitest"; +import { useDashboardStore } from "../store.js"; + +describe("mermaid popup slice", () => { + beforeEach(() => { + useDashboardStore.setState({ + mermaidPopupNodeId: null, + mermaidModalOpen: false, + }); + }); + + it("opens the popup with a node id", () => { + useDashboardStore.getState().openMermaidPopup("d1"); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1"); + }); + + it("closes the popup and the modal", () => { + useDashboardStore.setState({ + mermaidPopupNodeId: "d1", + mermaidModalOpen: true, + }); + useDashboardStore.getState().closeMermaidPopup(); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull(); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("opens and closes the modal independently", () => { + useDashboardStore.setState({ mermaidPopupNodeId: "d1" }); + useDashboardStore.getState().openMermaidModal(); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(true); + useDashboardStore.getState().closeMermaidModal(); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1"); + }); +}); +``` + +- [ ] **Step 2: Run, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- store.mermaid` +Expected: FAIL — `openMermaidPopup is not a function`. + +- [ ] **Step 3: Extend the store interface** + +Open `understand-anything-plugin/packages/dashboard/src/store.ts`. In the `DashboardStore` interface, add: + +```typescript + mermaidPopupNodeId: string | null; + mermaidModalOpen: boolean; + openMermaidPopup: (nodeId: string) => void; + closeMermaidPopup: () => void; + openMermaidModal: () => void; + closeMermaidModal: () => void; +``` + +In the `create((set) => ({ ... }))` body, add the initial state and actions (mirror the existing `codeViewer` pattern near lines 349–350): + +```typescript + mermaidPopupNodeId: null, + mermaidModalOpen: false, + openMermaidPopup: (nodeId) => + set({ mermaidPopupNodeId: nodeId, mermaidModalOpen: false }), + closeMermaidPopup: () => + set({ mermaidPopupNodeId: null, mermaidModalOpen: false }), + openMermaidModal: () => set({ mermaidModalOpen: true }), + closeMermaidModal: () => set({ mermaidModalOpen: false }), +``` + +- [ ] **Step 4: Run the test, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- store.mermaid` +Expected: PASS all 3 cases. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/store.ts \ + understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts +git commit -m "feat(dashboard): add MermaidPopupSlice to dashboard store" +``` + +--- + +## Phase G: MermaidRenderer Component + +### Task 10: Implement `MermaidRenderer` with 3 render states + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx` +- Create: `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx` + +- [ ] **Step 1: Write failing tests for all three states** + +Create `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx`: + +```tsx +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidRenderer } from "../MermaidRenderer.js"; + +vi.mock("mermaid", () => { + return { + default: { + initialize: vi.fn(), + render: vi.fn(async (id: string, source: string) => { + if (source.includes("INVALID_SYNTAX")) throw new Error("Parse error near INVALID_SYNTAX"); + return { svg: `` }; + }), + }, + }; +}); + +describe("MermaidRenderer", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders SVG for a valid source", async () => { + render(); + await waitFor(() => + expect(document.querySelector("svg[data-id]")).toBeInTheDocument(), + ); + }); + + it("falls back to
 on parse error with copy button", async () => {
+    render();
+    await waitFor(() =>
+      expect(screen.getByText(/Diagram render failed/i)).toBeInTheDocument(),
+    );
+    expect(screen.getByRole("button", { name: /copy source/i })).toBeEnabled();
+    expect(screen.getByText(/INVALID_SYNTAX/)).toBeInTheDocument();
+  });
+
+  it("copies source to clipboard on click", async () => {
+    const writeText = vi.fn().mockResolvedValue(undefined);
+    Object.defineProperty(navigator, "clipboard", {
+      value: { writeText },
+      writable: true,
+      configurable: true,
+    });
+    render();
+    await waitFor(() => screen.getByRole("button", { name: /copy source/i }));
+    await userEvent.click(screen.getByRole("button", { name: /copy source/i }));
+    expect(writeText).toHaveBeenCalledWith("flowchart TD\n  INVALID_SYNTAX");
+  });
+});
+```
+
+- [ ] **Step 2: Run, confirm failure**
+
+Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidRenderer`
+Expected: FAIL — module not found.
+
+- [ ] **Step 3: Implement the component**
+
+Create `understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx`:
+
+```tsx
+import { useEffect, useState } from "react";
+import { getMermaidTheme } from "../themes/mermaid-theme.js";
+import { onThemeChange } from "../themes/theme-engine.js";
+
+type RenderState =
+  | { kind: "loading" }
+  | { kind: "ok"; svg: string }
+  | { kind: "err"; msg: string }
+  | { kind: "chunk-err" };
+
+interface Props {
+  source: string;
+}
+
+export function MermaidRenderer({ source }: Props) {
+  const [state, setState] = useState({ kind: "loading" });
+  const [themeVersion, setThemeVersion] = useState(0);
+
+  useEffect(() => onThemeChange(() => setThemeVersion((v) => v + 1)), []);
+
+  useEffect(() => {
+    let cancelled = false;
+    (async () => {
+      setState({ kind: "loading" });
+      let mermaid;
+      try {
+        mermaid = (await import("mermaid")).default;
+      } catch {
+        if (!cancelled) setState({ kind: "chunk-err" });
+        return;
+      }
+      mermaid.initialize({
+        startOnLoad: false,
+        theme: "base",
+        themeVariables: getMermaidTheme() as unknown as Record,
+      });
+      try {
+        const id = `m-${crypto.randomUUID()}`;
+        const { svg } = await mermaid.render(id, source);
+        if (!cancelled) setState({ kind: "ok", svg });
+      } catch (err) {
+        if (!cancelled) setState({ kind: "err", msg: String((err as Error)?.message ?? err) });
+        console.warn("[mermaid-parse-error]", err);
+      }
+    })();
+    return () => {
+      cancelled = true;
+    };
+  }, [source, themeVersion]);
+
+  if (state.kind === "loading") {
+    return (
+      
+ Loading diagram… +
+ ); + } + + if (state.kind === "chunk-err") { + return ( +
+

+ ⚠ Failed to load diagram library +

+

+ Check your network and retry. +

+ +
+ ); + } + + if (state.kind === "err") { + return ( +
+

+ ⚠ Diagram render failed. Showing source instead. +

+
+          {source}
+        
+ +
+ ); + } + + return ( +
+ ); +} +``` + +- [ ] **Step 4: Run tests, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidRenderer` +Expected: PASS all 3 cases. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx \ + understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx +git commit -m "feat(dashboard): add MermaidRenderer component with fallbacks" +``` + +--- + +## Phase H: Collision-Detection Hook + +### Task 11: Implement `useMermaidPopupPosition` + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts` +- Create: `understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.test.ts` + +- [ ] **Step 1: Write failing tests — create 3 separate files (different mocks per file)** + +Because `vi.mock` is hoisted per-file, each collision scenario gets its own test file. Create all three: + +**File 1: `hooks/__tests__/useMermaidPopupPosition.empty.test.ts`** + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react"; + +vi.mock("@xyflow/react", () => ({ + useReactFlow: () => ({ + getNodes: () => [], + flowToScreenPosition: (p: { x: number; y: number }) => p, + }), +})); + +import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js"; + +describe("useMermaidPopupPosition · no nodes", () => { + it("returns bottom-center when no nodes present", () => { + const { result } = renderHook(() => + useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }), + ); + expect(result.current.x).toBe((1200 - 480) / 2); + expect(result.current.y).toBe(16); + }); +}); +``` + +**File 2: `hooks/__tests__/useMermaidPopupPosition.collision.test.ts`** + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react"; + +// A node sits exactly at bottom-center (blocking candidate 0). +// It does NOT block candidate 1 (offset −80 left). +vi.mock("@xyflow/react", () => ({ + useReactFlow: () => ({ + getNodes: () => [ + { id: "n1", position: { x: 380, y: 540 }, width: 440, height: 240 }, + ], + flowToScreenPosition: (p: { x: number; y: number }) => p, + }), +})); + +import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js"; + +describe("useMermaidPopupPosition · collision avoidance", () => { + it("shifts left by 80px when bottom-center is occupied", () => { + const { result } = renderHook(() => + useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }), + ); + expect(result.current.x).toBe((1200 - 480) / 2 - 80); + }); +}); +``` + +**File 3: `hooks/__tests__/useMermaidPopupPosition.fallback.test.ts`** + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react"; + +// One enormous node covers the entire container — every candidate collides. +vi.mock("@xyflow/react", () => ({ + useReactFlow: () => ({ + getNodes: () => [ + { id: "n1", position: { x: 0, y: 0 }, width: 1200, height: 800 }, + ], + flowToScreenPosition: (p: { x: number; y: number }) => p, + }), +})); + +import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js"; + +describe("useMermaidPopupPosition · full-collision fallback", () => { + it("falls back to bottom-center when all candidates are occupied", () => { + const { result } = renderHook(() => + useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }), + ); + expect(result.current.x).toBe((1200 - 480) / 2); + expect(result.current.y).toBe(16); + }); +}); +``` + +- [ ] **Step 2: Run, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- useMermaidPopupPosition` +Expected: FAIL — module not found. + +- [ ] **Step 3: Implement the hook** + +Create `understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts`: + +```typescript +import { useMemo } from "react"; +import { useReactFlow } from "@xyflow/react"; + +export interface PopupPosition { + x: number; + y: number; +} + +export interface ContainerBounds { + width: number; + height: number; +} + +const MARGIN = 16; +const HORIZONTAL_OFFSET = 80; + +interface Rect { + left: number; + top: number; + right: number; + bottom: number; +} + +function aabbIntersect(a: Rect, b: Rect): boolean { + return !( + a.right < b.left || + a.left > b.right || + a.bottom < b.top || + a.top > b.bottom + ); +} + +export function useMermaidPopupPosition( + width: number, + height: number, + container: ContainerBounds, +): PopupPosition { + const rf = useReactFlow(); + + return useMemo(() => { + const centerX = (container.width - width) / 2; + const candidates: PopupPosition[] = [ + { x: centerX, y: MARGIN }, + { x: centerX - HORIZONTAL_OFFSET, y: MARGIN }, + { x: centerX + HORIZONTAL_OFFSET, y: MARGIN }, + { x: MARGIN, y: MARGIN }, + { x: container.width - width - MARGIN, y: MARGIN }, + ]; + + const nodes = rf.getNodes(); + + for (const c of candidates) { + const popupRect: Rect = { + left: c.x, + right: c.x + width, + top: container.height - c.y - height, + bottom: container.height - c.y, + }; + const hit = nodes.some((n) => { + const topLeft = rf.flowToScreenPosition({ + x: n.position.x, + y: n.position.y, + }); + const bottomRight = rf.flowToScreenPosition({ + x: n.position.x + (n.width ?? 180), + y: n.position.y + (n.height ?? 80), + }); + return aabbIntersect(popupRect, { + left: topLeft.x, + top: topLeft.y, + right: bottomRight.x, + bottom: bottomRight.y, + }); + }); + if (!hit) return c; + } + return candidates[0]; + }, [width, height, container.width, container.height, rf]); +} +``` + +- [ ] **Step 4: Run the tests, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- useMermaidPopupPosition` +Expected: all test files PASS. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts \ + understand-anything-plugin/packages/dashboard/src/hooks/__tests__/ +git commit -m "feat(dashboard): add collision-avoiding popup position hook" +``` + +--- + +## Phase I: Popup Component + +### Task 12: Implement `MermaidFlowchartPopup` + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx` +- Create: `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx` + +- [ ] **Step 1: Write failing tests** + +Create `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx`: + +```tsx +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidFlowchartPopup } from "../MermaidFlowchartPopup.js"; +import { useDashboardStore } from "../../store.js"; + +vi.mock("mermaid", () => ({ + default: { + initialize: vi.fn(), + render: vi.fn(async () => ({ svg: "" })), + }, +})); + +vi.mock("@xyflow/react", () => ({ + useReactFlow: () => ({ + getNodes: () => [], + flowToScreenPosition: (p: { x: number; y: number }) => p, + }), +})); + +function seedGraph(mermaidValue?: string) { + useDashboardStore.setState({ + domainGraph: { + nodes: [ + { + id: "d1", + type: "domain", + name: "Checkout and Ordering", + summary: "", + tags: [], + complexity: "simple", + domainMeta: mermaidValue ? { mermaid: mermaidValue } : {}, + }, + ], + edges: [], + }, + mermaidPopupNodeId: "d1", + mermaidModalOpen: false, + }); +} + +describe("MermaidFlowchartPopup", () => { + beforeEach(() => { + seedGraph("flowchart TD\n A --> B"); + }); + + it("shows empty state and disables enlarge when no mermaid field", () => { + seedGraph(undefined); + render(); + expect(screen.getByText(/No detailed diagram/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /enlarge/i })).toBeDisabled(); + }); + + it("shows node name in header", () => { + render(); + expect(screen.getByText("Checkout and Ordering")).toBeInTheDocument(); + }); + + it("closes on X button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /close/i })); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull(); + }); + + it("opens modal on enlarge button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /enlarge/i })); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(true); + }); + + it("Esc does NOT close the popup", async () => { + render(); + await userEvent.keyboard("{Escape}"); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1"); + }); +}); +``` + +- [ ] **Step 2: Run, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidFlowchartPopup` +Expected: FAIL — module not found. + +- [ ] **Step 3: Implement the component** + +Create `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx`: + +```tsx +import { useEffect, useRef, useState } from "react"; +import { useDashboardStore } from "../store.js"; +import { MermaidRenderer } from "./MermaidRenderer.js"; +import { useMermaidPopupPosition } from "../hooks/useMermaidPopupPosition.js"; + +const POPUP_W = 480; +const POPUP_H = 260; + +export function MermaidFlowchartPopup() { + const nodeId = useDashboardStore((s) => s.mermaidPopupNodeId); + const graph = useDashboardStore((s) => s.domainGraph); + const closePopup = useDashboardStore((s) => s.closeMermaidPopup); + const openModal = useDashboardStore((s) => s.openMermaidModal); + + const containerRef = useRef(null); + const [bounds, setBounds] = useState({ width: 0, height: 0 }); + + useEffect(() => { + const el = containerRef.current?.parentElement; + if (!el) return; + const update = () => + setBounds({ width: el.clientWidth, height: el.clientHeight }); + update(); + const ro = new ResizeObserver(update); + ro.observe(el); + return () => ro.disconnect(); + }, [nodeId]); + + const position = useMermaidPopupPosition(POPUP_W, POPUP_H, bounds); + + if (!nodeId || !graph) return null; + const node = graph.nodes.find((n) => n.id === nodeId); + if (!node) return null; + const mermaidSource = node.domainMeta?.mermaid; + + return ( +
+
+ + {node.name} + +
+ + +
+
+
+ {mermaidSource ? ( + + ) : ( + + )} +
+
+ ); +} + +function EmptyState() { + return ( +
+

+ No detailed diagram yet +

+

+ Re-run{" "} + + /understand --full + {" "} + to generate the flowchart. +

+
+ ); +} +``` + +- [ ] **Step 4: Add the slide-up keyframe to the dashboard CSS** + +Open `understand-anything-plugin/packages/dashboard/src/index.css` and append at the end: + +```css +@keyframes mermaid-popup-slide-up { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} +``` + +- [ ] **Step 5: Run the tests, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidFlowchartPopup` +Expected: PASS all 5 cases. + +- [ ] **Step 6: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx \ + understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx \ + understand-anything-plugin/packages/dashboard/src/index.css +git commit -m "feat(dashboard): add MermaidFlowchartPopup slide-up component" +``` + +--- + +## Phase J: Modal Component + +### Task 13: Implement `MermaidFlowchartModal` + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx` +- Create: `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/package.json` — add `pako` for Mermaid Live URL encoding + +- [ ] **Step 1: Add `pako` dependency** + +Open `understand-anything-plugin/packages/dashboard/package.json` and add to `dependencies`: + +```json +"pako": "^2.1.0" +``` + +And to `devDependencies`: + +```json +"@types/pako": "^2.0.3" +``` + +Install: `cd understand-anything-plugin && pnpm install` + +- [ ] **Step 2: Write failing tests** + +Create `understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx`: + +```tsx +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidFlowchartModal } from "../MermaidFlowchartModal.js"; +import { useDashboardStore } from "../../store.js"; + +vi.mock("mermaid", () => ({ + default: { + initialize: vi.fn(), + render: vi.fn(async () => ({ svg: "" })), + }, +})); + +function seed() { + useDashboardStore.setState({ + domainGraph: { + nodes: [ + { + id: "d1", + type: "domain", + name: "Checkout and Ordering", + summary: "", + tags: [], + complexity: "simple", + domainMeta: { mermaid: "flowchart TD\n A --> B" }, + }, + ], + edges: [], + }, + mermaidPopupNodeId: "d1", + mermaidModalOpen: true, + }); +} + +describe("MermaidFlowchartModal", () => { + beforeEach(() => seed()); + + it("closes on Esc", async () => { + render(); + await userEvent.keyboard("{Escape}"); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("closes on X button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /close/i })); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("does NOT close on backdrop click", async () => { + render(); + await userEvent.click(screen.getByTestId("mermaid-modal-backdrop")); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(true); + }); + + it("copies source via Copy button", async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, "clipboard", { + value: { writeText }, + writable: true, + configurable: true, + }); + render(); + await userEvent.click(screen.getByRole("button", { name: /copy source/i })); + expect(writeText).toHaveBeenCalledWith("flowchart TD\n A --> B"); + }); + + it("generates an Open-in-Mermaid-Live href with pako-encoded source", () => { + render(); + const link = screen.getByRole("link", { name: /open in mermaid live/i }); + const href = link.getAttribute("href") ?? ""; + expect(href.startsWith("https://mermaid.live/edit#pako:")).toBe(true); + expect(href.length).toBeGreaterThan(40); + }); + + it("renders nothing when modal is closed", () => { + useDashboardStore.setState({ mermaidModalOpen: false }); + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); +}); +``` + +- [ ] **Step 3: Run, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidFlowchartModal` +Expected: FAIL — module not found. + +- [ ] **Step 4: Implement the modal** + +Create `understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx`: + +```tsx +import { useEffect } from "react"; +import { createPortal } from "react-dom"; +import pako from "pako"; +import { useDashboardStore } from "../store.js"; +import { MermaidRenderer } from "./MermaidRenderer.js"; + +function toPakoBase64(source: string): string { + const data = pako.deflate(new TextEncoder().encode(source), { level: 9 }); + let binary = ""; + for (const byte of data) binary += String.fromCharCode(byte); + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} + +function buildLiveUrl(source: string): string { + const state = { + code: source, + mermaid: { theme: "dark" }, + autoSync: true, + updateDiagram: true, + }; + const encoded = toPakoBase64(JSON.stringify(state)); + return `https://mermaid.live/edit#pako:${encoded}`; +} + +export function MermaidFlowchartModal() { + const open = useDashboardStore((s) => s.mermaidModalOpen); + const nodeId = useDashboardStore((s) => s.mermaidPopupNodeId); + const graph = useDashboardStore((s) => s.domainGraph); + const close = useDashboardStore((s) => s.closeMermaidModal); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") close(); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, close]); + + if (!open || !nodeId || !graph) return null; + const node = graph.nodes.find((n) => n.id === nodeId); + if (!node?.domainMeta?.mermaid) return null; + + const source = node.domainMeta.mermaid; + + return createPortal( +
+
+
+
+ + {node.name} + +
+ + + 🔗 Open in Mermaid Live + + +
+
+
+ +
+
+
, + document.body, + ); +} +``` + +- [ ] **Step 5: Run the tests, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- MermaidFlowchartModal` +Expected: PASS all 6 cases. + +- [ ] **Step 6: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx \ + understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx \ + understand-anything-plugin/packages/dashboard/package.json \ + understand-anything-plugin/pnpm-lock.yaml +git commit -m "feat(dashboard): add MermaidFlowchartModal with Copy + Mermaid Live link" +``` + +--- + +## Phase K: Integration Wiring + +### Task 14: Wire domain-node click → popup open + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx` + +- [ ] **Step 1: Add `openMermaidPopup` to the click handler** + +Open `understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx`. Find the `selectNode` subscription block (around lines 18–20) and add: + +```typescript +const openMermaidPopup = useDashboardStore((s) => s.openMermaidPopup); +``` + +Find the `onClick` handler (around line 30) and change it to: + +```tsx +onClick={() => { + selectNode(data.domainId); + openMermaidPopup(data.domainId); +}} +``` + +- [ ] **Step 2: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx +git commit -m "feat(dashboard): open mermaid popup on domain node click" +``` + +--- + +### Task 15: Mount popup + modal inside `DomainGraphView` + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx` + +- [ ] **Step 1: Import the new components** + +At the top of `DomainGraphView.tsx`, add imports: + +```tsx +import { MermaidFlowchartPopup } from "./MermaidFlowchartPopup.js"; +import { MermaidFlowchartModal } from "./MermaidFlowchartModal.js"; +``` + +- [ ] **Step 2: Mount them inside the graph container** + +Find the root JSX returned by `DomainGraphView` (likely a `
` wrapping the `` instance — must be `position: relative` for absolute-positioned popup to anchor correctly). + +Ensure that outer div has `className="relative ..."` (add `relative` if missing). Immediately after the `` block, add: + +```tsx + + +``` + +Example resulting structure: + +```tsx +return ( +
+ + + +
+); +``` + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx +git commit -m "feat(dashboard): mount MermaidFlowchartPopup and Modal in DomainGraphView" +``` + +--- + +### Task 16: Close popup on view-mode switch + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/store.ts` +- Create/modify: `understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts` (extend) + +- [ ] **Step 1: Write a failing test for the view-switch behavior** + +Append to `understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts`: + +```typescript +describe("mermaid popup closes on view switch", () => { + it("clears popup when switching to structural view", () => { + useDashboardStore.setState({ + mermaidPopupNodeId: "d1", + mermaidModalOpen: true, + }); + useDashboardStore.getState().setViewMode("structural"); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull(); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("clears popup when switching to knowledge view", () => { + useDashboardStore.setState({ + mermaidPopupNodeId: "d1", + mermaidModalOpen: false, + }); + useDashboardStore.getState().setViewMode("knowledge"); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull(); + }); +}); +``` + +- [ ] **Step 2: Run, confirm failure** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- store.mermaid` +Expected: FAIL on the two new cases. + +- [ ] **Step 3: Modify `setViewMode` to close the popup** + +In `store.ts`, locate the existing `setViewMode` action. **First read the current body** — it may already perform side-effects (e.g. resetting `selectedNodeId`, resetting filters). Preserve all of them. + +Modify the action so that, when the new mode is not `"domain"`, mermaid popup state is reset. Example (preserve existing fields in the `set({...})` payload, just add two more): + +```typescript +setViewMode: (mode) => + set((state) => { + const nextMermaidPopupNodeId = + mode === "domain" ? state.mermaidPopupNodeId : null; + const nextMermaidModalOpen = + mode === "domain" ? state.mermaidModalOpen : false; + return { + // ...all existing fields the original setViewMode returned... + viewMode: mode, + mermaidPopupNodeId: nextMermaidPopupNodeId, + mermaidModalOpen: nextMermaidModalOpen, + }; + }), +``` + +If the original was just `setViewMode: (mode) => set({ viewMode: mode })`, the full replacement is: + +```typescript +setViewMode: (mode) => + set((state) => ({ + viewMode: mode, + mermaidPopupNodeId: mode === "domain" ? state.mermaidPopupNodeId : null, + mermaidModalOpen: mode === "domain" ? state.mermaidModalOpen : false, + })), +``` + +- [ ] **Step 4: Run, confirm pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test -- store.mermaid` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/store.ts \ + understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts +git commit -m "feat(dashboard): auto-close mermaid popup on non-domain view switch" +``` + +--- + +### Task 17: Verify lazy-chunk split in production build + +**Files:** +- None (inspection only) + +- [ ] **Step 1: Build the dashboard** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` +Expected: success. + +- [ ] **Step 2: Inspect the output** + +Run: `ls -la understand-anything-plugin/packages/dashboard/dist/assets/ | grep -i mermaid` +Expected: at least one JS chunk whose filename contains `mermaid` OR a chunk listed as a dynamic import in the build summary. The dashboard's main entry chunk should NOT contain the mermaid code. + +If no such chunk appears, verify `vite.config.ts` does not force-inline dynamic imports; this is unlikely with defaults. If verification fails, investigate before proceeding. + +- [ ] **Step 3: (Optional) Sanity check with a bundle analyzer** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build -- --mode analyze` (if an analyzer is configured) — otherwise skip. + +--- + +## Phase L: Full Test Suite + Lint + +### Task 18: Run the complete test matrix + lint + +- [ ] **Step 1: Core tests** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test` +Expected: all PASS. + +- [ ] **Step 2: Dashboard tests** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard test` +Expected: all PASS. + +- [ ] **Step 3: Lint** + +Run: `cd understand-anything-plugin && pnpm lint` +Expected: no errors. Fix any new lint warnings inline before continuing. + +- [ ] **Step 4: Type check (build everything)** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core build && pnpm --filter @understand-anything/dashboard build` +Expected: both succeed. + +- [ ] **Step 5: If any step failed, fix and commit the fix separately** + +Only continue once all four commands are green. + +--- + +## Phase M: Manual Acceptance + +### Task 19: Run the 11-step acceptance checklist + +**Prerequisite:** A local clone of a suitable domain-rich sample project. The spec points at `microservices-demo`; use that or any project with at least 2–3 domain nodes. + +- [ ] **Step 1:** Run `/understand --full` in the sample project. Expected: `knowledge-graph.json` written, domain nodes with non-empty `domainMeta.mermaid` where applicable. + +- [ ] **Step 2:** Run `/understand-dashboard` → switch to Domain view. + +- [ ] **Step 3:** Click on a domain node (e.g. "Checkout and Ordering"). Expected: popup slides up from the bottom-center of the graph area over 300 ms; header shows the node name; body renders a Mermaid SVG; API-call arrows show labels like `|POST /charge|`. + +- [ ] **Step 4:** Verify the popup avoids nearby visible nodes (pan the graph so a node sits at the bottom-center first, then click a different domain node — popup should shift to one of the 5 candidate positions). + +- [ ] **Step 5:** Click ⤢. Expected: fullscreen modal opens over a black/80 backdrop. + +- [ ] **Step 6:** In the modal, click 📋 Copy source. Expected: Mermaid source in clipboard (paste into a text editor to verify). + +- [ ] **Step 7:** In the modal, click 🔗 Open in Mermaid Live. Expected: new tab opens at `mermaid.live` with the source pre-loaded. + +- [ ] **Step 8:** Press Esc. Expected: modal closes; popup remains open. + +- [ ] **Step 9:** Click ✕ on the popup. Expected: popup closes. + +- [ ] **Step 10:** Hand-edit `.understand-anything/knowledge-graph.json` — delete `domainMeta.mermaid` from one node, save, reload the dashboard. Click that node. Expected: empty state ("No detailed diagram yet…"); ⤢ is disabled. + +- [ ] **Step 11:** Hand-edit `.understand-anything/knowledge-graph.json` — corrupt one node's `mermaid` source (e.g. replace with `"flowchart TD\n --->>invalid"`), save, reload. Click that node. Expected: `
` fallback with source shown, Copy source button available, console warn logged.
+
+- [ ] **Step 12:** Switch to Structural view while popup is open. Expected: popup auto-closes.
+
+- [ ] **Step 13:** Open DevTools → Network tab → fresh reload the dashboard → switch to Domain view but do NOT click any node. Expected: no network request for the `mermaid` chunk yet. Click a domain node. Expected: a network request for the `mermaid-*.js` chunk appears (lazy loading confirmed).
+
+- [ ] **Step 14:** If any step fails, open an issue in the plan's scope (fix or document as known-limitation). Only close the plan once all 13 steps pass.
+
+---
+
+## Phase N: Release Versioning
+
+### Task 20: Bump versions across all four files
+
+**Reference:** `CLAUDE.md` §Versioning
+
+**Files:**
+- Modify: `understand-anything-plugin/package.json`
+- Modify: `.claude-plugin/marketplace.json`
+- Modify: `.claude-plugin/plugin.json`
+- Modify: `.cursor-plugin/plugin.json`
+
+- [ ] **Step 1: Pick the next minor version**
+
+Run: `node -p "require('./understand-anything-plugin/package.json').version"` → note the current version. The next version is `MAJOR.MINOR+1.0`.
+
+- [ ] **Step 2: Update all four files to the new version**
+
+Update the `version` field in each file to the next minor version. Example (if current is 1.5.0, new is 1.6.0):
+
+```json
+{ "version": "1.6.0" }
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add understand-anything-plugin/package.json \
+        .claude-plugin/marketplace.json \
+        .claude-plugin/plugin.json \
+        .cursor-plugin/plugin.json
+git commit -m "chore(release): bump version for Mermaid flowchart popup feature"
+```
+
+---
+
+## Self-Review Checklist
+
+Before handing off to execution, verify:
+
+- [ ] Every spec requirement is implemented by at least one task:
+  - Schema extension → Task 1
+  - `sanitizeGraph` preserves field → Task 2
+  - Agent Mermaid generation → Task 4
+  - Assemble-reviewer dry-run validation → Tasks 3 + 5
+  - Dashboard `mermaid` lazy import → Task 10
+  - MermaidRenderer 3 states → Task 10
+  - Popup 5-candidate collision avoidance → Task 11 + Task 12
+  - Popup empty-state + disabled enlarge → Task 12
+  - Popup slide-up animation → Task 12
+  - Esc does NOT close popup → Task 12 test
+  - Modal Copy + Open in Live + Esc + ✕ → Task 13
+  - Backdrop-click does NOT close modal → Task 13 test
+  - View switch closes popup → Task 16
+  - Theme-change re-renders Mermaid → Task 7 + Task 10 (themeVersion)
+  - Testing across 3 layers → Tasks 1, 9–13, 16
+  - Manual acceptance checklist → Task 19
+  - Version bump across 4 files → Task 20
+
+- [ ] No placeholders ("TBD", "fill in"), no "similar to", no empty code blocks.
+
+- [ ] Function / field names consistent throughout:
+  - Store fields: `mermaidPopupNodeId`, `mermaidModalOpen`
+  - Store actions: `openMermaidPopup`, `closeMermaidPopup`, `openMermaidModal`, `closeMermaidModal`
+  - Schema field: `domainMeta.mermaid`
+
+- [ ] All commit messages follow the project's conventional-commit style observed in `git log` (`feat(scope):`, `chore(scope):`, `test(scope):`, `docs(scope):`).
+
+---
+
+## Execution Handoff
+
+Plan complete and saved to `docs/superpowers/plans/2026-04-20-mermaid-service-flowchart.md`. Two execution options:
+
+1. **Subagent-Driven (recommended)** — dispatch a fresh subagent per task, review between tasks, fast iteration.
+2. **Inline Execution** — execute tasks in this session with checkpoints for review.
+
+Which approach?
diff --git a/docs/superpowers/specs/2026-04-20-mermaid-service-flowchart-design.md b/docs/superpowers/specs/2026-04-20-mermaid-service-flowchart-design.md
new file mode 100644
index 000000000..c27dd6a1f
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-20-mermaid-service-flowchart-design.md
@@ -0,0 +1,358 @@
+# Mermaid Service Flowchart Popup — Design
+
+**Date:** 2026-04-20
+**Status:** Draft (pending implementation plan)
+**Scope:** Understand-Anything dashboard · Domain view
+
+---
+
+## Summary
+
+When a user clicks a `domain` node in the Domain graph, a small 480×260 popup slides up from the bottom-center of the graph area rendering a Mermaid flowchart of the domain's internal business logic. The popup has a fullscreen button and a close button in its top-right corner; the fullscreen modal supports source copy and an "Open in Mermaid Live" external link. Mermaid source is generated offline by the `domain-analyzer` agent and persisted to `knowledge-graph.json`. The dashboard is a pure renderer — no runtime LLM calls.
+
+## Goals
+
+- Let engineers drill from "where does this concept live" to "how does it actually work" without leaving the dashboard
+- Make domain-level business logic reviewable in one glance (flowchart with API labels on edges)
+- Zero runtime LLM cost: diagrams are generated offline and stored in the knowledge graph
+- No regression to existing Domain / Structural / Knowledge views
+
+## Non-Goals
+
+- Flowcharts for `flow` or `step` node types (too granular; domain-level is the right abstraction)
+- Flowcharts for Structural or Knowledge view nodes
+- Multiple Mermaid diagram types (`sequenceDiagram`, `classDiagram`, etc.) — only `flowchart TD` in v1
+- Runtime LLM regeneration of Mermaid from a "refresh" button
+- Pan/zoom inside the fullscreen modal (rely on browser scroll + container `overflow: auto`)
+- Visual regression testing infrastructure (Chromatic, Percy)
+
+---
+
+## Architecture & Data Flow
+
+```
+/understand-domain
+   └─► domain-analyzer agent
+         ├─ extracts business domains / flows / steps
+         └─ for each domain node, emits Mermaid flowchart source
+            (nodes = logical steps; arrows labeled with API calls where applicable)
+         ▼
+   .understand-anything/intermediate/domain-analysis.json
+         ▼
+   merge-batch-graphs.py  (existing; preserves unknown fields)
+         ▼
+   assemble-reviewer agent
+         ├─ schema validation (incl. mermaid field non-empty string if present)
+         └─ mermaid.parse() dry-run in Node — on error, delete field + log
+         ▼
+   graph-reviewer agent  (inline by default)
+         ▼
+   .understand-anything/knowledge-graph.json
+       nodes[*].domainMeta.mermaid?: string
+         ▼
+Dashboard (packages/dashboard)
+   └─ Domain view → user clicks domain node
+         ▼
+   MermaidFlowchartPopup (lazy-imports mermaid)
+       ├─ on success:        render SVG
+       ├─ on missing field:  empty state + "re-run /understand --full"
+       └─ on parse failure:  
 source fallback + Copy button
+```
+
+**Invariants:**
+- Dashboard imports from `core` only via browser-safe subpaths (`./schema`, `./types`, `./search`)
+- `mermaid` npm package is lazy-imported on first popup open; excluded from main bundle
+- No network requests at runtime except the user-initiated "Open in Mermaid Live" external link
+- Structural and Knowledge views are untouched
+
+---
+
+## Schema & Data Model
+
+### Node extension (`packages/core/src/types.ts`)
+
+```typescript
+export interface DomainMeta {
+  entities?: string[];
+  businessRules?: string[];
+  crossDomainInteractions?: string[];
+  entryPoint?: string;
+  entryType?: "http" | "cli" | "event" | "cron" | "manual";
+  mermaid?: string;   // NEW: Mermaid flowchart source for this domain
+}
+```
+
+### Schema validation (`packages/core/src/schema.ts`)
+
+Add to `DomainMetaSchema`:
+
+```typescript
+mermaid: z.string().min(1).optional()
+```
+
+- `optional` — historical data without the field must validate
+- `min(1)` — if present, must not be an empty string
+- `sanitizeGraph()` / `autoFixGraph()` need no changes (they pass optional fields through)
+
+### Persisted shape
+
+```json
+{
+  "id": "domain-checkout-ordering",
+  "type": "domain",
+  "name": "Checkout and Ordering",
+  "summary": "...",
+  "domainMeta": {
+    "entities": ["Order", "OrderResult"],
+    "businessRules": ["..."],
+    "mermaid": "flowchart TD\n  A[Cart Retrieval] -->|GET /cart| B[Product Lookup]\n  ..."
+  }
+}
+```
+
+---
+
+## Agent Changes
+
+### `understand-anything-plugin/agents/domain-analyzer.md`
+
+Append a Mermaid generation section to the agent prompt:
+
+> For each `domain` node produced, also emit `domainMeta.mermaid` — a Mermaid `flowchart TD` source string representing the internal business logic of that domain. Rules:
+> - Each logical step/component in the domain is a flowchart node (e.g. "Cart Retrieval", "Currency Convert", "Payment Charge")
+> - Edges represent control/data flow between steps
+> - **When an edge corresponds to an API call, label the arrow with the API** using `|METHOD /path|` syntax (e.g. `A -->|POST /charge| B`)
+> - Keep node labels concise (≤40 chars); use `[...]` for process nodes, `{...}` for decisions
+> - 5–15 nodes per domain; if more, group related steps into subgraphs
+> - Output valid Mermaid syntax — will be dry-run parsed before commit
+
+### `understand-anything-plugin/agents/assemble-reviewer.md`
+
+Append a Mermaid validation step:
+
+> After schema validation, for every node with `domainMeta.mermaid`, dry-run-parse the source. Implementation: the agent uses the Bash tool to run a small Node one-liner (`node -e "require('mermaid').parse(...)"`) or a dedicated script added at `understand-anything-plugin/scripts/validate-mermaid.mjs`. On parse error, **delete that node's `mermaid` field** (do not abort the whole assembly) and log `[mermaid-invalid] : ` to `.understand-anything/intermediate/mermaid-errors.log`. Continue processing remaining nodes.
+
+This guarantees `knowledge-graph.json` cannot carry broken Mermaid sources into the dashboard.
+
+---
+
+## Dashboard Implementation
+
+### File layout
+
+```
+packages/dashboard/src/
+  components/
+    MermaidFlowchartPopup.tsx        NEW  (480×260 slide-up)
+    MermaidFlowchartModal.tsx        NEW  (fullscreen modal)
+    MermaidRenderer.tsx              NEW  (shared source-to-SVG)
+    DomainClusterNode.tsx            EDIT (trigger popup on click)
+    DomainGraphView.tsx              EDIT (mount popup portal)
+  store.ts                           EDIT (popup + modal state)
+  hooks/
+    useMermaidPopupPosition.ts       NEW  (collision-avoiding position hook)
+  themes/
+    mermaid-theme.ts                 NEW  (CSS-vars → Mermaid themeVariables)
+```
+
+### Store extension (`store.ts`)
+
+```typescript
+interface MermaidPopupSlice {
+  mermaidPopupNodeId: string | null;
+  mermaidModalOpen: boolean;
+  openMermaidPopup: (nodeId: string) => void;
+  closeMermaidPopup: () => void;
+  openMermaidModal: () => void;
+  closeMermaidModal: () => void;
+}
+```
+
+- Clicking a `domain` node calls both `selectNode(id)` (existing) and `openMermaidPopup(id)` (new)
+- Rapid clicks between nodes replace `mermaidPopupNodeId` in place (no multiple popups)
+- Switching view mode (`setViewMode`) auto-calls `closeMermaidPopup()`
+
+### `MermaidFlowchartPopup.tsx`
+
+Layout:
+
+```
+┌─────────────────────────────────────┐ 480 × 260
+│ Checkout and Ordering       ⤢  ✕   │  header
+├─────────────────────────────────────┤
+│        [Mermaid SVG rendered]       │  body, overflow: auto
+└─────────────────────────────────────┘
+```
+
+- Positioned via React Portal to a graph-container overlay div (fixed within container)
+- Background `var(--color-elevated)`, border `1px solid var(--color-border-subtle)`, shadow `var(--shadow-xl)`
+- Enter animation: `translateY(100%) → 0`, 300ms ease-out
+- Body content comes from ``
+- ⤢ button → `openMermaidModal()`
+- ✕ button → `closeMermaidPopup()`
+- Esc does **not** close the popup (Esc only closes the modal — distinct behaviors)
+
+### `useMermaidPopupPosition.ts`
+
+Candidate position sequence:
+
+1. Bottom-center
+2. Bottom-center offset −80px horizontally
+3. Bottom-center offset +80px horizontally
+4. Bottom-left (`margin: 16`)
+5. Bottom-right (`margin: 16`)
+
+Pick the first candidate whose AABB does not intersect any visible node's on-screen bounding box (derived via `useReactFlow().flowToScreenPosition()`). If all candidates collide, fall back to bottom-center and accept overlap.
+
+Viewport changes (pan/zoom) re-run the check, throttled to `requestAnimationFrame`.
+
+### `MermaidRenderer.tsx`
+
+```typescript
+export function MermaidRenderer({ source }: { source: string }) {
+  // state: loading | ok(svg) | err(message)
+  useEffect(() => {
+    const mermaid = (await import('mermaid')).default;   // LAZY
+    mermaid.initialize({ startOnLoad: false, theme: 'base', themeVariables: getMermaidTheme() });
+    try {
+      const id = `m-${crypto.randomUUID()}`;
+      const { svg } = await mermaid.render(id, source);
+      setState({ kind: 'ok', svg });
+    } catch (e) {
+      setState({ kind: 'err', msg: String(e) });
+    }
+  }, [source]);
+  ...
+}
+```
+
+- Lazy-import prevents the ~400KB mermaid dep from entering the main bundle
+- Parse error → fallback to `
` source + Copy button (see Error Handling)
+- Chunk load error → retry button
+
+### `MermaidFlowchartModal.tsx`
+
+- Portal to `document.body`, full viewport, backdrop `rgba(0,0,0,0.8)`
+- Contents: centered SVG (same renderer, re-rendered at larger container size), actions top-right
+- Actions:
+  - 📋 **Copy source** — `navigator.clipboard.writeText(source)`, toast on success
+  - 🔗 **Open in Mermaid Live** — `https://mermaid.live/edit#pako:${pakoBase64(source)}` (deflate-base64url per Mermaid Live's format)
+  - ✕ Close
+- Close via Esc (keydown listener mounted while open) or ✕ button. No click-outside-to-close.
+- Container `overflow: auto` lets large diagrams scroll; no pan/zoom library in v1
+
+### Theme integration (`themes/mermaid-theme.ts`)
+
+```typescript
+export function getMermaidTheme() {
+  const style = getComputedStyle(document.documentElement);
+  const read = (v: string) => style.getPropertyValue(v).trim();
+  return {
+    background: read('--color-elevated'),
+    primaryColor: read('--color-accent-dim'),
+    primaryBorderColor: read('--color-accent'),
+    primaryTextColor: read('--color-text-primary'),
+    lineColor: read('--color-accent-dim'),
+    secondaryColor: read('--color-surface'),
+    tertiaryColor: read('--color-root'),
+    edgeLabelBackground: read('--color-elevated'),
+    mainBkg: read('--color-elevated'),
+    fontFamily: read('--font-sans'),   // 复用现有 CSS 变量
+    fontSize: '14px',
+  };
+}
+```
+
+- Subscribe to `theme-engine`'s change event; on change, bump a store version counter that `MermaidRenderer` uses as an effect dep → re-render with new theme
+- Mermaid's fontFamily reads from the existing `--font-sans` CSS variable (Inter) rather than the serif header font; readability at small flowchart-node sizes is better with a sans-serif
+
+---
+
+## Error Handling
+
+| Failure | Detection | User-facing behavior | Dev-facing signal |
+|---|---|---|---|
+| Node missing `mermaid` field | `node.domainMeta?.mermaid` is falsy | Popup opens; body shows "No detailed diagram yet. Re-run `/understand --full` to generate the flowchart." ⤢ button disabled with tooltip "Nothing to enlarge" | None |
+| Mermaid parse error | `mermaid.render()` throws | "⚠ Diagram render failed. Showing source instead." + `
` source + [Copy source] button | `console.warn("[mermaid-parse-error] " + err.message)` |
+| `mermaid` chunk load error | `await import('mermaid')` throws `ChunkLoadError` | "⚠ Failed to load diagram library. Check your network and retry." + [Retry] button | Default Vite chunk-error behavior |
+
+**Edge cases handled:**
+
+- Rapid clicks across domain nodes → `mermaidPopupNodeId` replaced in place
+- View switch while popup open → auto-close
+- ReactFlow viewport changes → position recomputed, rAF-throttled
+- Modal Esc listener → mounted only while modal is open
+- Extremely long Mermaid source (>50KB) → trusted from agent pipeline; dashboard does not enforce a length cap
+- Multiple dashboard tabs → each has its own Zustand store, no cross-tab coupling
+
+---
+
+## Testing Strategy
+
+Three layers, all using Vitest (existing setup).
+
+### Schema contract (`packages/core/tests/schema.mermaid.test.ts`)
+
+- Valid graph with `domainMeta.mermaid` — passes
+- Empty string `mermaid` — rejected
+- Missing `mermaid` — passes (optional)
+- `sanitizeGraph()` preserves the field
+
+### Component tests (`packages/dashboard/src/components/__tests__/`)
+
+**`MermaidRenderer.test.tsx`**
+- Valid source → rendered SVG in DOM
+- Invalid source (mocked `mermaid.render` rejection) → `
` + Copy button
+- Mocked `import('mermaid')` throwing `ChunkLoadError` → Retry UI
+
+**`MermaidFlowchartPopup.test.tsx`**
+- No `domainMeta.mermaid` → empty-state text + ⤢ disabled
+- Header shows `node.name`
+- Esc keydown does **not** close the popup (only the modal)
+
+### Hook test (`hooks/__tests__/useMermaidPopupPosition.test.ts`)
+
+- No nodes → bottom-center returned
+- Only bottom-center occupied → returns offset −80
+- All five candidates occupied → falls back to bottom-center
+- `useReactFlow` viewport change triggers recomputation
+
+### Deliberately out of scope
+
+- Visual correctness of mermaid-rendered SVGs (trust mermaid.js)
+- LLM output quality (handled by agent prompt + assemble-reviewer dry-run)
+- Screenshot / visual regression (no existing infra in project)
+
+### Manual acceptance (run once before shipping)
+
+1. Run `/understand --full` on `microservices-demo` sample
+2. Open `/understand-dashboard`, switch to Domain view
+3. Click "Checkout and Ordering" → popup slides up from bottom-center
+4. Verify flowchart renders, API labels visible on arrows
+5. Click ⤢ → modal opens; verify Copy source + Open in Mermaid Live work
+6. Press Esc → modal closes, popup remains
+7. Click ✕ on popup → popup closes
+8. Hand-edit knowledge-graph.json to remove `mermaid` from one domain → reload → verify empty state
+9. Hand-edit knowledge-graph.json to corrupt Mermaid syntax → reload → verify `
` fallback
+10. Switch to Structural view while popup open → verify popup auto-closes
+11. Inspect Vite build output → verify `mermaid` is in a lazy chunk, not main
+
+---
+
+## Open questions
+
+None at design freeze.
+
+## Rollout
+
+- Ship in a single release (no feature flag) — additive to existing Domain view
+- Version bump across all four files per `CLAUDE.md` versioning section
+- Bump is a minor version (new feature, no breaking change)
+- No migration needed: missing `mermaid` fields on existing graphs fall back gracefully
+
+## References
+
+- Issue: inline in brainstorming transcript (2026-04-20)
+- Related source files enumerated in "Dashboard Implementation" above
+- `CLAUDE.md` §Gotchas: browser-safe imports from `core`
+- `CLAUDE.md` §Versioning: four files to sync
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1d5c2b878..aeb0aeb80 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,7 +13,7 @@ importers:
         version: 5.9.3
       vitest:
         specifier: ^3.1.0
-        version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
   homepage:
     dependencies:
@@ -26,6 +26,9 @@ importers:
       '@understand-anything/core':
         specifier: workspace:*
         version: link:packages/core
+      mermaid:
+        specifier: ^10.9.0
+        version: 10.9.5
     devDependencies:
       '@types/node':
         specifier: ^22.0.0
@@ -35,7 +38,7 @@ importers:
         version: 5.9.3
       vitest:
         specifier: ^3.1.0
-        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
   understand-anything-plugin/packages/core:
     dependencies:
@@ -90,13 +93,13 @@ importers:
         version: 25.5.0
       '@vitest/coverage-v8':
         specifier: 3.2.4
-        version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
+        version: 3.2.4(vitest@3.2.4)
       typescript:
         specifier: ^5.7.0
         version: 5.9.3
       vitest:
         specifier: ^3.1.0
-        version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
   understand-anything-plugin/packages/dashboard:
     dependencies:
@@ -118,6 +121,12 @@ importers:
       hast-util-to-jsx-runtime:
         specifier: ^2.3.6
         version: 2.3.6
+      mermaid:
+        specifier: ^10.9.0
+        version: 10.9.5
+      pako:
+        specifier: ^2.1.0
+        version: 2.1.0
       react:
         specifier: ^19.0.0
         version: 19.2.4
@@ -134,9 +143,21 @@ importers:
       '@tailwindcss/vite':
         specifier: ^4.0.0
         version: 4.2.1(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
+      '@testing-library/jest-dom':
+        specifier: ^6.6.0
+        version: 6.9.1
+      '@testing-library/react':
+        specifier: ^16.1.0
+        version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+      '@testing-library/user-event':
+        specifier: ^14.5.0
+        version: 14.6.1(@testing-library/dom@10.4.1)
       '@types/d3-force':
         specifier: ^3.0.10
         version: 3.0.10
+      '@types/pako':
+        specifier: ^2.0.3
+        version: 2.0.4
       '@types/react':
         specifier: ^19.0.0
         version: 19.2.14
@@ -146,6 +167,12 @@ importers:
       '@vitejs/plugin-react':
         specifier: ^4.3.0
         version: 4.7.0(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
+      '@vitest/ui':
+        specifier: ^3.1.0
+        version: 3.2.4(vitest@3.2.4)
+      jsdom:
+        specifier: ^25.0.0
+        version: 25.0.1
       tailwindcss:
         specifier: ^4.0.0
         version: 4.2.1
@@ -155,13 +182,22 @@ importers:
       vite:
         specifier: ^6.0.0
         version: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
+      vitest:
+        specifier: ^3.1.0
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
 packages:
 
+  '@adobe/css-tools@4.4.4':
+    resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+
   '@ampproject/remapping@2.3.0':
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
 
+  '@asamuzakjp/css-color@3.2.0':
+    resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
   '@astrojs/compiler@3.0.0':
     resolution: {integrity: sha512-MwAbDE5mawZ1SS+D8qWiHdprdME5Tlj2e0YjxnEICvcOpbSukNS7Sa7hA5PK+6RrmUr/t6Gi5YgrdZKjbO/WPQ==}
 
@@ -250,6 +286,10 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
+  '@babel/runtime@7.29.2':
+    resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/template@7.28.6':
     resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
     engines: {node: '>=6.9.0'}
@@ -266,6 +306,9 @@ packages:
     resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
     engines: {node: '>=18'}
 
+  '@braintree/sanitize-url@6.0.4':
+    resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
+
   '@capsizecss/unpack@4.0.0':
     resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==}
     engines: {node: '>=18'}
@@ -276,6 +319,34 @@ packages:
   '@clack/prompts@1.1.0':
     resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
 
+  '@csstools/color-helpers@5.1.0':
+    resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+    engines: {node: '>=18'}
+
+  '@csstools/css-calc@2.1.4':
+    resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-color-parser@3.1.0':
+    resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-parser-algorithms@3.0.5':
+    resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-tokenizer@3.0.4':
+    resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+    engines: {node: '>=18'}
+
   '@dagrejs/dagre@2.0.4':
     resolution: {integrity: sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==}
 
@@ -765,6 +836,9 @@ packages:
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
 
+  '@polka/url@1.0.0-next.29':
+    resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
   '@rolldown/pluginutils@1.0.0-beta.27':
     resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
 
@@ -1023,6 +1097,38 @@ packages:
     peerDependencies:
       vite: ^5.2.0 || ^6 || ^7
 
+  '@testing-library/dom@10.4.1':
+    resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
+    engines: {node: '>=18'}
+
+  '@testing-library/jest-dom@6.9.1':
+    resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
+    engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+  '@testing-library/react@16.3.2':
+    resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@testing-library/dom': ^10.0.0
+      '@types/react': ^18.0.0 || ^19.0.0
+      '@types/react-dom': ^18.0.0 || ^19.0.0
+      react: ^18.0.0 || ^19.0.0
+      react-dom: ^18.0.0 || ^19.0.0
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@testing-library/user-event@14.6.1':
+    resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==}
+    engines: {node: '>=12', npm: '>=6'}
+    peerDependencies:
+      '@testing-library/dom': '>=7.21.4'
+
+  '@types/aria-query@5.0.4':
+    resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
   '@types/babel__core@7.20.5':
     resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
 
@@ -1050,9 +1156,18 @@ packages:
   '@types/d3-interpolate@3.0.4':
     resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
 
+  '@types/d3-scale-chromatic@3.1.0':
+    resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
+  '@types/d3-scale@4.0.9':
+    resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
   '@types/d3-selection@3.0.11':
     resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
 
+  '@types/d3-time@3.0.4':
+    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
   '@types/d3-transition@3.0.9':
     resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
 
@@ -1074,6 +1189,9 @@ packages:
   '@types/hast@3.0.4':
     resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
 
+  '@types/mdast@3.0.15':
+    resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
+
   '@types/mdast@4.0.4':
     resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
 
@@ -1089,6 +1207,9 @@ packages:
   '@types/node@25.5.0':
     resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
 
+  '@types/pako@2.0.4':
+    resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
+
   '@types/react-dom@19.2.3':
     resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
     peerDependencies:
@@ -1097,6 +1218,9 @@ packages:
   '@types/react@19.2.14':
     resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
 
+  '@types/trusted-types@2.0.7':
+    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
   '@types/unist@2.0.11':
     resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
 
@@ -1147,6 +1271,11 @@ packages:
   '@vitest/spy@3.2.4':
     resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
 
+  '@vitest/ui@3.2.4':
+    resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==}
+    peerDependencies:
+      vitest: 3.2.4
+
   '@vitest/utils@3.2.4':
     resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
 
@@ -1159,6 +1288,10 @@ packages:
   '@xyflow/system@0.0.75':
     resolution: {integrity: sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==}
 
+  agent-base@7.1.4:
+    resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+    engines: {node: '>= 14'}
+
   ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -1171,6 +1304,10 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
+  ansi-styles@5.2.0:
+    resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+    engines: {node: '>=10'}
+
   ansi-styles@6.2.3:
     resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
     engines: {node: '>=12'}
@@ -1182,6 +1319,9 @@ packages:
   argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
+  aria-query@5.3.0:
+    resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
   aria-query@5.3.2:
     resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
     engines: {node: '>= 0.4'}
@@ -1201,6 +1341,9 @@ packages:
     engines: {node: ^20.19.1 || >=22.12.0, npm: '>=9.6.5', pnpm: '>=7.1.0'}
     hasBin: true
 
+  asynckit@0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
   axobject-query@4.1.0:
     resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
     engines: {node: '>= 0.4'}
@@ -1239,6 +1382,10 @@ packages:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
     engines: {node: '>=8'}
 
+  call-bind-apply-helpers@1.0.2:
+    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+    engines: {node: '>= 0.4'}
+
   caniuse-lite@1.0.30001787:
     resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
 
@@ -1287,6 +1434,10 @@ packages:
   color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 
+  combined-stream@1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+
   comma-separated-tokens@2.0.3:
     resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
 
@@ -1294,6 +1445,14 @@ packages:
     resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
     engines: {node: '>=16'}
 
+  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'}
+
   common-ancestor-path@2.0.0:
     resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==}
     engines: {node: '>= 18'}
@@ -1308,6 +1467,9 @@ packages:
     resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
     engines: {node: '>=18'}
 
+  cose-base@1.0.3:
+    resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
   cross-spawn@7.0.6:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
@@ -1330,17 +1492,60 @@ packages:
     resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
     engines: {node: '>= 6'}
 
+  css.escape@1.5.1:
+    resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
   csso@5.0.5:
     resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
     engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
 
+  cssstyle@4.6.0:
+    resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+    engines: {node: '>=18'}
+
   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@3.33.2:
+    resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==}
+    engines: {node: '>=0.10'}
+
+  d3-array@2.12.1:
+    resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-axis@3.0.0:
+    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+    engines: {node: '>=12'}
+
+  d3-brush@3.0.0:
+    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+    engines: {node: '>=12'}
+
+  d3-chord@3.0.1:
+    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+    engines: {node: '>=12'}
+
   d3-color@3.1.0:
     resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
     engines: {node: '>=12'}
 
+  d3-contour@4.0.2:
+    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+    engines: {node: '>=12'}
+
+  d3-delaunay@6.0.4:
+    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+    engines: {node: '>=12'}
+
   d3-dispatch@3.0.1:
     resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
     engines: {node: '>=12'}
@@ -1349,26 +1554,88 @@ packages:
     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'}
@@ -1383,6 +1650,20 @@ packages:
     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.13:
+    resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
+
+  data-urls@5.0.0:
+    resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+    engines: {node: '>=18'}
+
+  dayjs@1.11.20:
+    resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
+
   debug@4.4.3:
     resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
     engines: {node: '>=6.0'}
@@ -1392,6 +1673,9 @@ packages:
       supports-color:
         optional: true
 
+  decimal.js@10.6.0:
+    resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
   decode-named-character-reference@1.3.0:
     resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
 
@@ -1402,6 +1686,13 @@ packages:
   defu@6.1.4:
     resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
 
+  delaunator@5.1.0:
+    resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+
+  delayed-stream@1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+
   dequal@2.0.3:
     resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
     engines: {node: '>=6'}
@@ -1419,6 +1710,10 @@ packages:
   devlop@1.1.0:
     resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
 
+  diff@5.2.2:
+    resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==}
+    engines: {node: '>=0.3.1'}
+
   diff@8.0.3:
     resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
     engines: {node: '>=0.3.1'}
@@ -1426,6 +1721,12 @@ packages:
   dlv@1.1.3:
     resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
 
+  dom-accessibility-api@0.5.16:
+    resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+  dom-accessibility-api@0.6.3:
+    resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
   dom-serializer@2.0.0:
     resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
 
@@ -1436,6 +1737,9 @@ packages:
     resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
     engines: {node: '>= 4'}
 
+  dompurify@3.4.1:
+    resolution: {integrity: sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==}
+
   domutils@3.2.2:
     resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
 
@@ -1443,12 +1747,19 @@ packages:
     resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
     engines: {node: '>=4'}
 
+  dunder-proto@1.0.1:
+    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+    engines: {node: '>= 0.4'}
+
   eastasianwidth@0.2.0:
     resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
 
   electron-to-chromium@1.5.335:
     resolution: {integrity: sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==}
 
+  elkjs@0.9.3:
+    resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==}
+
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
@@ -1467,12 +1778,28 @@ packages:
     resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
     engines: {node: '>=0.12'}
 
+  es-define-property@1.0.1:
+    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+    engines: {node: '>= 0.4'}
+
+  es-errors@1.3.0:
+    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+    engines: {node: '>= 0.4'}
+
   es-module-lexer@1.7.0:
     resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
 
   es-module-lexer@2.0.0:
     resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
 
+  es-object-atoms@1.1.1:
+    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+    engines: {node: '>= 0.4'}
+
+  es-set-tostringtag@2.1.0:
+    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+    engines: {node: '>= 0.4'}
+
   esbuild@0.25.12:
     resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
     engines: {node: '>=18'}
@@ -1519,6 +1846,12 @@ packages:
       picomatch:
         optional: true
 
+  fflate@0.8.2:
+    resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
+  flatted@3.4.2:
+    resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
+
   flattie@1.1.1:
     resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
     engines: {node: '>=8'}
@@ -1534,11 +1867,18 @@ packages:
     resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
     engines: {node: '>=14'}
 
+  form-data@4.0.5:
+    resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+    engines: {node: '>= 6'}
+
   fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
 
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
   fuse.js@7.1.0:
     resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==}
     engines: {node: '>=10'}
@@ -1547,6 +1887,14 @@ packages:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
 
+  get-intrinsic@1.3.0:
+    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+    engines: {node: '>= 0.4'}
+
+  get-proto@1.0.1:
+    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+    engines: {node: '>= 0.4'}
+
   github-slugger@2.0.0:
     resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
 
@@ -1555,6 +1903,10 @@ packages:
     deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
     hasBin: true
 
+  gopd@1.2.0:
+    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+    engines: {node: '>= 0.4'}
+
   graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
@@ -1565,6 +1917,18 @@ packages:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
 
+  has-symbols@1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+    engines: {node: '>= 0.4'}
+
+  has-tostringtag@1.0.2:
+    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+    engines: {node: '>= 0.4'}
+
+  hasown@2.0.3:
+    resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
+    engines: {node: '>= 0.4'}
+
   hast-util-from-html@2.0.3:
     resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
 
@@ -1598,6 +1962,10 @@ packages:
   hastscript@9.0.1:
     resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
 
+  html-encoding-sniffer@4.0.0:
+    resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+    engines: {node: '>=18'}
+
   html-escaper@2.0.2:
     resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
 
@@ -1613,13 +1981,36 @@ packages:
   http-cache-semantics@4.2.0:
     resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
 
+  http-proxy-agent@7.0.2:
+    resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+    engines: {node: '>= 14'}
+
+  https-proxy-agent@7.0.6:
+    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'}
+
   ignore@7.0.5:
     resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
     engines: {node: '>= 4'}
 
+  indent-string@4.0.0:
+    resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+    engines: {node: '>=8'}
+
   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'}
+
   iron-webcrypto@1.2.1:
     resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
 
@@ -1653,6 +2044,9 @@ packages:
     resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
     engines: {node: '>=12'}
 
+  is-potential-custom-element-name@1.0.1:
+    resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
   is-wsl@3.1.1:
     resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
     engines: {node: '>=16'}
@@ -1696,6 +2090,15 @@ packages:
     resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
     hasBin: true
 
+  jsdom@25.0.1:
+    resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      canvas: ^2.11.2
+    peerDependenciesMeta:
+      canvas:
+        optional: true
+
   jsesc@3.1.0:
     resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
     engines: {node: '>=6'}
@@ -1706,6 +2109,20 @@ packages:
     engines: {node: '>=6'}
     hasBin: true
 
+  katex@0.16.45:
+    resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==}
+    hasBin: true
+
+  khroma@2.1.0:
+    resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
+
+  kleur@4.1.5:
+    resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+    engines: {node: '>=6'}
+
+  layout-base@1.0.2:
+    resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
   lightningcss-android-arm64@1.31.1:
     resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==}
     engines: {node: '>= 12.0.0'}
@@ -1846,6 +2263,9 @@ packages:
     resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
     engines: {node: '>= 12.0.0'}
 
+  lodash-es@4.18.1:
+    resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
+
   longest-streak@3.1.0:
     resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
 
@@ -1862,6 +2282,10 @@ packages:
   lru-cache@5.1.1:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
 
+  lz-string@1.5.0:
+    resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+    hasBin: true
+
   magic-string@0.30.21:
     resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
 
@@ -1878,12 +2302,19 @@ packages:
   markdown-table@3.0.4:
     resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
 
+  math-intrinsics@1.1.0:
+    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+    engines: {node: '>= 0.4'}
+
   mdast-util-definitions@6.0.0:
     resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==}
 
   mdast-util-find-and-replace@3.0.2:
     resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
 
+  mdast-util-from-markdown@1.3.1:
+    resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==}
+
   mdast-util-from-markdown@2.0.3:
     resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
 
@@ -1923,6 +2354,9 @@ packages:
   mdast-util-to-markdown@2.1.2:
     resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
 
+  mdast-util-to-string@3.2.0:
+    resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
+
   mdast-util-to-string@4.0.0:
     resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
 
@@ -1932,6 +2366,12 @@ packages:
   mdn-data@2.27.1:
     resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
 
+  mermaid@10.9.5:
+    resolution: {integrity: sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ==}
+
+  micromark-core-commonmark@1.1.0:
+    resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
+
   micromark-core-commonmark@2.0.3:
     resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
 
@@ -1956,66 +2396,138 @@ packages:
   micromark-extension-gfm@3.0.0:
     resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
 
+  micromark-factory-destination@1.1.0:
+    resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
+
   micromark-factory-destination@2.0.1:
     resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
 
+  micromark-factory-label@1.1.0:
+    resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==}
+
   micromark-factory-label@2.0.1:
     resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
 
+  micromark-factory-space@1.1.0:
+    resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==}
+
   micromark-factory-space@2.0.1:
     resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
 
+  micromark-factory-title@1.1.0:
+    resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==}
+
   micromark-factory-title@2.0.1:
     resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
 
+  micromark-factory-whitespace@1.1.0:
+    resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==}
+
   micromark-factory-whitespace@2.0.1:
     resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
 
+  micromark-util-character@1.2.0:
+    resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==}
+
   micromark-util-character@2.1.1:
     resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
 
+  micromark-util-chunked@1.1.0:
+    resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==}
+
   micromark-util-chunked@2.0.1:
     resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
 
+  micromark-util-classify-character@1.1.0:
+    resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==}
+
   micromark-util-classify-character@2.0.1:
     resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
 
+  micromark-util-combine-extensions@1.1.0:
+    resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==}
+
   micromark-util-combine-extensions@2.0.1:
     resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
 
+  micromark-util-decode-numeric-character-reference@1.1.0:
+    resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==}
+
   micromark-util-decode-numeric-character-reference@2.0.2:
     resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
 
+  micromark-util-decode-string@1.1.0:
+    resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==}
+
   micromark-util-decode-string@2.0.1:
     resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
 
+  micromark-util-encode@1.1.0:
+    resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==}
+
   micromark-util-encode@2.0.1:
     resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
 
+  micromark-util-html-tag-name@1.2.0:
+    resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==}
+
   micromark-util-html-tag-name@2.0.1:
     resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
 
+  micromark-util-normalize-identifier@1.1.0:
+    resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==}
+
   micromark-util-normalize-identifier@2.0.1:
     resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
 
+  micromark-util-resolve-all@1.1.0:
+    resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==}
+
   micromark-util-resolve-all@2.0.1:
     resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
 
+  micromark-util-sanitize-uri@1.2.0:
+    resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==}
+
   micromark-util-sanitize-uri@2.0.1:
     resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
 
+  micromark-util-subtokenize@1.1.0:
+    resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==}
+
   micromark-util-subtokenize@2.1.0:
     resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
 
+  micromark-util-symbol@1.1.0:
+    resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==}
+
   micromark-util-symbol@2.0.1:
     resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
 
+  micromark-util-types@1.1.0:
+    resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==}
+
   micromark-util-types@2.0.2:
     resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
 
+  micromark@3.2.0:
+    resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==}
+
   micromark@4.0.2:
     resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
 
+  mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+
+  min-indent@1.0.1:
+    resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+    engines: {node: '>=4'}
+
   minimatch@10.2.4:
     resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
     engines: {node: 18 || 20 || >=22}
@@ -2028,6 +2540,10 @@ packages:
     resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
     engines: {node: '>=16 || 14 >=14.17'}
 
+  mri@1.2.0:
+    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+    engines: {node: '>=4'}
+
   mrmime@2.0.1:
     resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
     engines: {node: '>=10'}
@@ -2064,6 +2580,9 @@ packages:
   node-releases@2.0.37:
     resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
 
+  non-layered-tidy-tree-layout@2.0.2:
+    resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
+
   normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -2071,6 +2590,9 @@ packages:
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
+  nwsapi@2.2.23:
+    resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
+
   obug@2.1.1:
     resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
 
@@ -2104,6 +2626,9 @@ packages:
   package-manager-detector@1.6.0:
     resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
 
+  pako@2.1.0:
+    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+
   parse-entities@4.0.2:
     resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
 
@@ -2150,6 +2675,10 @@ packages:
     resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
     engines: {node: ^10 || ^12 || >=14}
 
+  pretty-format@27.5.1:
+    resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
   prismjs@1.30.0:
     resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
     engines: {node: '>=6'}
@@ -2157,6 +2686,10 @@ packages:
   property-information@7.1.0:
     resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
 
+  punycode@2.3.1:
+    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+    engines: {node: '>=6'}
+
   radix3@1.1.2:
     resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
 
@@ -2165,6 +2698,9 @@ packages:
     peerDependencies:
       react: ^19.2.4
 
+  react-is@17.0.2:
+    resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
   react-markdown@10.1.0:
     resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
     peerDependencies:
@@ -2183,6 +2719,10 @@ packages:
     resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
     engines: {node: '>= 20.19.0'}
 
+  redent@3.0.0:
+    resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+    engines: {node: '>=8'}
+
   regex-recursion@6.0.2:
     resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
 
@@ -2232,15 +2772,38 @@ packages:
   retext@9.0.0:
     resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==}
 
+  robust-predicates@3.0.3:
+    resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
+
   rollup@4.59.0:
     resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
+  rrweb-cssom@0.7.1:
+    resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+
+  rrweb-cssom@0.8.0:
+    resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+  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==}
+
   sax@1.5.0:
     resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==}
     engines: {node: '>=11.0.0'}
 
+  saxes@6.0.0:
+    resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+    engines: {node: '>=v12.22.7'}
+
   scheduler@0.27.0:
     resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
 
@@ -2276,6 +2839,10 @@ packages:
     resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
     engines: {node: '>=14'}
 
+  sirv@3.0.2:
+    resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
+    engines: {node: '>=18'}
+
   sisteransi@1.0.5:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
 
@@ -2315,6 +2882,10 @@ packages:
     resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
     engines: {node: '>=12'}
 
+  strip-indent@3.0.0:
+    resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+    engines: {node: '>=8'}
+
   strip-literal@3.1.0:
     resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
 
@@ -2324,6 +2895,9 @@ packages:
   style-to-object@1.0.14:
     resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
 
+  stylis@4.4.0:
+    resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
+
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -2333,6 +2907,9 @@ packages:
     engines: {node: '>=16'}
     hasBin: true
 
+  symbol-tree@3.2.4:
+    resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
   tailwindcss@4.2.1:
     resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
 
@@ -2377,6 +2954,25 @@ packages:
     resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
     engines: {node: '>=14.0.0'}
 
+  tldts-core@6.1.86:
+    resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+  tldts@6.1.86:
+    resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+    hasBin: true
+
+  totalist@3.0.1:
+    resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+    engines: {node: '>=6'}
+
+  tough-cookie@5.1.2:
+    resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+    engines: {node: '>=16'}
+
+  tr46@5.1.1:
+    resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+    engines: {node: '>=18'}
+
   tree-sitter-c-sharp@0.23.5:
     resolution: {integrity: sha512-xJGOeXPMmld0nES5+080N/06yY6LQi+KWGWV4LfZaZe6srJPtUtfhIbRSN7EZN6IaauzW28v6W4QHFwmeUW6HQ==}
     peerDependencies:
@@ -2479,6 +3075,10 @@ packages:
   trough@2.2.0:
     resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
 
+  ts-dedent@2.2.0:
+    resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
+    engines: {node: '>=6.10'}
+
   tsconfck@3.1.6:
     resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
     engines: {node: ^18 || >=20}
@@ -2533,6 +3133,9 @@ packages:
   unist-util-remove-position@5.0.0:
     resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
 
+  unist-util-stringify-position@3.0.3:
+    resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
+
   unist-util-stringify-position@4.0.0:
     resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
 
@@ -2618,6 +3221,15 @@ packages:
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
 
+  uuid@9.0.1:
+    resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+    hasBin: true
+
+  uvu@0.5.6:
+    resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
+    engines: {node: '>=8'}
+    hasBin: true
+
   vfile-location@5.0.3:
     resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
 
@@ -2748,19 +3360,43 @@ packages:
       jsdom:
         optional: true
 
+  w3c-xmlserializer@5.0.0:
+    resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+    engines: {node: '>=18'}
+
   web-namespaces@2.0.1:
     resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
 
   web-tree-sitter@0.26.6:
     resolution: {integrity: sha512-fSPR7VBW/fZQdUSp/bXTDLT+i/9dwtbnqgEBMzowrM4U3DzeCwDbY3MKo0584uQxID4m/1xpLflrlT/rLIRPew==}
 
-  which-pm-runs@1.1.0:
-    resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
-    engines: {node: '>=4'}
+  web-worker@1.5.0:
+    resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
 
-  which@2.0.2:
-    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
-    engines: {node: '>= 8'}
+  webidl-conversions@7.0.0:
+    resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+    engines: {node: '>=12'}
+
+  whatwg-encoding@3.1.1:
+    resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+    engines: {node: '>=18'}
+    deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+  whatwg-mimetype@4.0.0:
+    resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+    engines: {node: '>=18'}
+
+  whatwg-url@14.2.0:
+    resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+    engines: {node: '>=18'}
+
+  which-pm-runs@1.1.0:
+    resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
+    engines: {node: '>=4'}
+
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
     hasBin: true
 
   why-is-node-running@2.3.0:
@@ -2776,6 +3412,25 @@ packages:
     resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
     engines: {node: '>=12'}
 
+  ws@8.20.0:
+    resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  xml-name-validator@5.0.0:
+    resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+    engines: {node: '>=18'}
+
+  xmlchars@2.2.0:
+    resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
   xxhash-wasm@1.1.0:
     resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==}
 
@@ -2836,11 +3491,21 @@ packages:
 
 snapshots:
 
+  '@adobe/css-tools@4.4.4': {}
+
   '@ampproject/remapping@2.3.0':
     dependencies:
       '@jridgewell/gen-mapping': 0.3.13
       '@jridgewell/trace-mapping': 0.3.31
 
+  '@asamuzakjp/css-color@3.2.0':
+    dependencies:
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+      lru-cache: 10.4.3
+
   '@astrojs/compiler@3.0.0': {}
 
   '@astrojs/internal-helpers@0.8.0':
@@ -2977,6 +3642,8 @@ snapshots:
       '@babel/core': 7.29.0
       '@babel/helper-plugin-utils': 7.28.6
 
+  '@babel/runtime@7.29.2': {}
+
   '@babel/template@7.28.6':
     dependencies:
       '@babel/code-frame': 7.29.0
@@ -3002,6 +3669,8 @@ snapshots:
 
   '@bcoe/v8-coverage@1.0.2': {}
 
+  '@braintree/sanitize-url@6.0.4': {}
+
   '@capsizecss/unpack@4.0.0':
     dependencies:
       fontkitten: 1.0.3
@@ -3015,6 +3684,26 @@ snapshots:
       '@clack/core': 1.1.0
       sisteransi: 1.0.5
 
+  '@csstools/color-helpers@5.1.0': {}
+
+  '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+
+  '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/color-helpers': 5.1.0
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+
+  '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-tokenizer': 3.0.4
+
+  '@csstools/css-tokenizer@3.0.4': {}
+
   '@dagrejs/dagre@2.0.4':
     dependencies:
       '@dagrejs/graphlib': 3.0.4
@@ -3314,6 +4003,8 @@ snapshots:
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
+  '@polka/url@1.0.0-next.29': {}
+
   '@rolldown/pluginutils@1.0.0-beta.27': {}
 
   '@rollup/pluginutils@5.3.0(rollup@4.59.0)':
@@ -3507,6 +4198,42 @@ snapshots:
       tailwindcss: 4.2.1
       vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
+  '@testing-library/dom@10.4.1':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/runtime': 7.29.2
+      '@types/aria-query': 5.0.4
+      aria-query: 5.3.0
+      dom-accessibility-api: 0.5.16
+      lz-string: 1.5.0
+      picocolors: 1.1.1
+      pretty-format: 27.5.1
+
+  '@testing-library/jest-dom@6.9.1':
+    dependencies:
+      '@adobe/css-tools': 4.4.4
+      aria-query: 5.3.2
+      css.escape: 1.5.1
+      dom-accessibility-api: 0.6.3
+      picocolors: 1.1.1
+      redent: 3.0.0
+
+  '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+    dependencies:
+      '@babel/runtime': 7.29.2
+      '@testing-library/dom': 10.4.1
+      react: 19.2.4
+      react-dom: 19.2.4(react@19.2.4)
+    optionalDependencies:
+      '@types/react': 19.2.14
+      '@types/react-dom': 19.2.3(@types/react@19.2.14)
+
+  '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
+    dependencies:
+      '@testing-library/dom': 10.4.1
+
+  '@types/aria-query@5.0.4': {}
+
   '@types/babel__core@7.20.5':
     dependencies:
       '@babel/parser': 7.29.0
@@ -3545,8 +4272,16 @@ snapshots:
     dependencies:
       '@types/d3-color': 3.1.3
 
+  '@types/d3-scale-chromatic@3.1.0': {}
+
+  '@types/d3-scale@4.0.9':
+    dependencies:
+      '@types/d3-time': 3.0.4
+
   '@types/d3-selection@3.0.11': {}
 
+  '@types/d3-time@3.0.4': {}
+
   '@types/d3-transition@3.0.9':
     dependencies:
       '@types/d3-selection': 3.0.11
@@ -3572,6 +4307,10 @@ snapshots:
     dependencies:
       '@types/unist': 3.0.3
 
+  '@types/mdast@3.0.15':
+    dependencies:
+      '@types/unist': 2.0.11
+
   '@types/mdast@4.0.4':
     dependencies:
       '@types/unist': 3.0.3
@@ -3590,6 +4329,8 @@ snapshots:
     dependencies:
       undici-types: 7.18.2
 
+  '@types/pako@2.0.4': {}
+
   '@types/react-dom@19.2.3(@types/react@19.2.14)':
     dependencies:
       '@types/react': 19.2.14
@@ -3598,6 +4339,9 @@ snapshots:
     dependencies:
       csstype: 3.2.3
 
+  '@types/trusted-types@2.0.7':
+    optional: true
+
   '@types/unist@2.0.11': {}
 
   '@types/unist@3.0.3': {}
@@ -3616,7 +4360,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
+  '@vitest/coverage-v8@3.2.4(vitest@3.2.4)':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 1.0.2
@@ -3631,7 +4375,7 @@ snapshots:
       std-env: 3.10.0
       test-exclude: 7.0.2
       tinyrainbow: 2.0.0
-      vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
+      vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
     transitivePeerDependencies:
       - supports-color
 
@@ -3679,6 +4423,17 @@ snapshots:
     dependencies:
       tinyspy: 4.0.4
 
+  '@vitest/ui@3.2.4(vitest@3.2.4)':
+    dependencies:
+      '@vitest/utils': 3.2.4
+      fflate: 0.8.2
+      flatted: 3.4.2
+      pathe: 2.0.3
+      sirv: 3.0.2
+      tinyglobby: 0.2.15
+      tinyrainbow: 2.0.0
+      vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3)
+
   '@vitest/utils@3.2.4':
     dependencies:
       '@vitest/pretty-format': 3.2.4
@@ -3708,6 +4463,8 @@ snapshots:
       d3-selection: 3.0.0
       d3-zoom: 3.0.0
 
+  agent-base@7.1.4: {}
+
   ansi-regex@5.0.1: {}
 
   ansi-regex@6.2.2: {}
@@ -3716,6 +4473,8 @@ snapshots:
     dependencies:
       color-convert: 2.0.1
 
+  ansi-styles@5.2.0: {}
+
   ansi-styles@6.2.3: {}
 
   anymatch@3.1.3:
@@ -3725,6 +4484,10 @@ snapshots:
 
   argparse@2.0.1: {}
 
+  aria-query@5.3.0:
+    dependencies:
+      dequal: 2.0.3
+
   aria-query@5.3.2: {}
 
   array-iterate@2.0.1: {}
@@ -3831,6 +4594,8 @@ snapshots:
       - uploadthing
       - yaml
 
+  asynckit@0.4.0: {}
+
   axobject-query@4.1.0: {}
 
   bail@2.0.2: {}
@@ -3861,6 +4626,11 @@ snapshots:
 
   cac@6.7.14: {}
 
+  call-bind-apply-helpers@1.0.2:
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+
   caniuse-lite@1.0.30001787: {}
 
   ccount@2.0.1: {}
@@ -3899,10 +4669,18 @@ snapshots:
 
   color-name@1.1.4: {}
 
+  combined-stream@1.0.8:
+    dependencies:
+      delayed-stream: 1.0.0
+
   comma-separated-tokens@2.0.3: {}
 
   commander@11.1.0: {}
 
+  commander@7.2.0: {}
+
+  commander@8.3.0: {}
+
   common-ancestor-path@2.0.0: {}
 
   convert-source-map@2.0.0: {}
@@ -3911,6 +4689,10 @@ snapshots:
 
   cookie@1.1.1: {}
 
+  cose-base@1.0.3:
+    dependencies:
+      layout-base: 1.0.2
+
   cross-spawn@7.0.6:
     dependencies:
       path-key: 3.1.1
@@ -3941,14 +4723,58 @@ snapshots:
 
   css-what@6.2.2: {}
 
+  css.escape@1.5.1: {}
+
   csso@5.0.5:
     dependencies:
       css-tree: 2.2.1
 
+  cssstyle@4.6.0:
+    dependencies:
+      '@asamuzakjp/css-color': 3.2.0
+      rrweb-cssom: 0.8.0
+
   csstype@3.2.3: {}
 
+  cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2):
+    dependencies:
+      cose-base: 1.0.3
+      cytoscape: 3.33.2
+
+  cytoscape@3.33.2: {}
+
+  d3-array@2.12.1:
+    dependencies:
+      internmap: 1.0.1
+
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-axis@3.0.0: {}
+
+  d3-brush@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
+  d3-chord@3.0.1:
+    dependencies:
+      d3-path: 3.1.0
+
   d3-color@3.1.0: {}
 
+  d3-contour@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-delaunay@6.0.4:
+    dependencies:
+      delaunator: 5.1.0
+
   d3-dispatch@3.0.1: {}
 
   d3-drag@3.0.0:
@@ -3956,22 +4782,82 @@ snapshots:
       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):
@@ -3991,10 +4877,57 @@ snapshots:
       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.13:
+    dependencies:
+      d3: 7.9.0
+      lodash-es: 4.18.1
+
+  data-urls@5.0.0:
+    dependencies:
+      whatwg-mimetype: 4.0.0
+      whatwg-url: 14.2.0
+
+  dayjs@1.11.20: {}
+
   debug@4.4.3:
     dependencies:
       ms: 2.1.3
 
+  decimal.js@10.6.0: {}
+
   decode-named-character-reference@1.3.0:
     dependencies:
       character-entities: 2.0.2
@@ -4003,6 +4936,12 @@ snapshots:
 
   defu@6.1.4: {}
 
+  delaunator@5.1.0:
+    dependencies:
+      robust-predicates: 3.0.3
+
+  delayed-stream@1.0.0: {}
+
   dequal@2.0.3: {}
 
   destr@2.0.5: {}
@@ -4015,10 +4954,16 @@ snapshots:
     dependencies:
       dequal: 2.0.3
 
+  diff@5.2.2: {}
+
   diff@8.0.3: {}
 
   dlv@1.1.3: {}
 
+  dom-accessibility-api@0.5.16: {}
+
+  dom-accessibility-api@0.6.3: {}
+
   dom-serializer@2.0.0:
     dependencies:
       domelementtype: 2.3.0
@@ -4031,6 +4976,10 @@ snapshots:
     dependencies:
       domelementtype: 2.3.0
 
+  dompurify@3.4.1:
+    optionalDependencies:
+      '@types/trusted-types': 2.0.7
+
   domutils@3.2.2:
     dependencies:
       dom-serializer: 2.0.0
@@ -4039,10 +4988,18 @@ snapshots:
 
   dset@3.1.4: {}
 
+  dunder-proto@1.0.1:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-errors: 1.3.0
+      gopd: 1.2.0
+
   eastasianwidth@0.2.0: {}
 
   electron-to-chromium@1.5.335: {}
 
+  elkjs@0.9.3: {}
+
   emoji-regex@8.0.0: {}
 
   emoji-regex@9.2.2: {}
@@ -4056,10 +5013,25 @@ snapshots:
 
   entities@6.0.1: {}
 
+  es-define-property@1.0.1: {}
+
+  es-errors@1.3.0: {}
+
   es-module-lexer@1.7.0: {}
 
   es-module-lexer@2.0.0: {}
 
+  es-object-atoms@1.1.1:
+    dependencies:
+      es-errors: 1.3.0
+
+  es-set-tostringtag@2.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      has-tostringtag: 1.0.2
+      hasown: 2.0.3
+
   esbuild@0.25.12:
     optionalDependencies:
       '@esbuild/aix-ppc64': 0.25.12
@@ -4144,6 +5116,10 @@ snapshots:
     optionalDependencies:
       picomatch: 4.0.4
 
+  fflate@0.8.2: {}
+
+  flatted@3.4.2: {}
+
   flattie@1.1.1: {}
 
   fontace@0.4.1:
@@ -4159,13 +5135,41 @@ snapshots:
       cross-spawn: 7.0.6
       signal-exit: 4.1.0
 
+  form-data@4.0.5:
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      es-set-tostringtag: 2.1.0
+      hasown: 2.0.3
+      mime-types: 2.1.35
+
   fsevents@2.3.3:
     optional: true
 
+  function-bind@1.1.2: {}
+
   fuse.js@7.1.0: {}
 
   gensync@1.0.0-beta.2: {}
 
+  get-intrinsic@1.3.0:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      function-bind: 1.1.2
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-symbols: 1.1.0
+      hasown: 2.0.3
+      math-intrinsics: 1.1.0
+
+  get-proto@1.0.1:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-object-atoms: 1.1.1
+
   github-slugger@2.0.0: {}
 
   glob@10.5.0:
@@ -4177,6 +5181,8 @@ snapshots:
       package-json-from-dist: 1.0.1
       path-scurry: 1.11.1
 
+  gopd@1.2.0: {}
+
   graceful-fs@4.2.11: {}
 
   h3@1.15.6:
@@ -4193,6 +5199,16 @@ snapshots:
 
   has-flag@4.0.0: {}
 
+  has-symbols@1.1.0: {}
+
+  has-tostringtag@1.0.2:
+    dependencies:
+      has-symbols: 1.1.0
+
+  hasown@2.0.3:
+    dependencies:
+      function-bind: 1.1.2
+
   hast-util-from-html@2.0.3:
     dependencies:
       '@types/hast': 3.0.4
@@ -4300,6 +5316,10 @@ snapshots:
       property-information: 7.1.0
       space-separated-tokens: 2.0.2
 
+  html-encoding-sniffer@4.0.0:
+    dependencies:
+      whatwg-encoding: 3.1.1
+
   html-escaper@2.0.2: {}
 
   html-escaper@3.0.3: {}
@@ -4310,10 +5330,34 @@ snapshots:
 
   http-cache-semantics@4.2.0: {}
 
+  http-proxy-agent@7.0.2:
+    dependencies:
+      agent-base: 7.1.4
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  https-proxy-agent@7.0.6:
+    dependencies:
+      agent-base: 7.1.4
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  iconv-lite@0.6.3:
+    dependencies:
+      safer-buffer: 2.1.2
+
   ignore@7.0.5: {}
 
+  indent-string@4.0.0: {}
+
   inline-style-parser@0.2.7: {}
 
+  internmap@1.0.1: {}
+
+  internmap@2.0.3: {}
+
   iron-webcrypto@1.2.1: {}
 
   is-alphabetical@2.0.1: {}
@@ -4337,6 +5381,8 @@ snapshots:
 
   is-plain-obj@4.1.0: {}
 
+  is-potential-custom-element-name@1.0.1: {}
+
   is-wsl@3.1.1:
     dependencies:
       is-inside-container: 1.0.0
@@ -4382,10 +5428,48 @@ snapshots:
     dependencies:
       argparse: 2.0.1
 
+  jsdom@25.0.1:
+    dependencies:
+      cssstyle: 4.6.0
+      data-urls: 5.0.0
+      decimal.js: 10.6.0
+      form-data: 4.0.5
+      html-encoding-sniffer: 4.0.0
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.6
+      is-potential-custom-element-name: 1.0.1
+      nwsapi: 2.2.23
+      parse5: 7.3.0
+      rrweb-cssom: 0.7.1
+      saxes: 6.0.0
+      symbol-tree: 3.2.4
+      tough-cookie: 5.1.2
+      w3c-xmlserializer: 5.0.0
+      webidl-conversions: 7.0.0
+      whatwg-encoding: 3.1.1
+      whatwg-mimetype: 4.0.0
+      whatwg-url: 14.2.0
+      ws: 8.20.0
+      xml-name-validator: 5.0.0
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
   jsesc@3.1.0: {}
 
   json5@2.2.3: {}
 
+  katex@0.16.45:
+    dependencies:
+      commander: 8.3.0
+
+  khroma@2.1.0: {}
+
+  kleur@4.1.5: {}
+
+  layout-base@1.0.2: {}
+
   lightningcss-android-arm64@1.31.1:
     optional: true
 
@@ -4485,6 +5569,8 @@ snapshots:
       lightningcss-win32-x64-msvc: 1.32.0
     optional: true
 
+  lodash-es@4.18.1: {}
+
   longest-streak@3.1.0: {}
 
   loupe@3.2.1: {}
@@ -4497,6 +5583,8 @@ snapshots:
     dependencies:
       yallist: 3.1.1
 
+  lz-string@1.5.0: {}
+
   magic-string@0.30.21:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.5
@@ -4519,6 +5607,8 @@ snapshots:
 
   markdown-table@3.0.4: {}
 
+  math-intrinsics@1.1.0: {}
+
   mdast-util-definitions@6.0.0:
     dependencies:
       '@types/mdast': 4.0.4
@@ -4532,6 +5622,23 @@ snapshots:
       unist-util-is: 6.0.1
       unist-util-visit-parents: 6.0.2
 
+  mdast-util-from-markdown@1.3.1:
+    dependencies:
+      '@types/mdast': 3.0.15
+      '@types/unist': 2.0.11
+      decode-named-character-reference: 1.3.0
+      mdast-util-to-string: 3.2.0
+      micromark: 3.2.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-decode-string: 1.1.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      unist-util-stringify-position: 3.0.3
+      uvu: 0.5.6
+    transitivePeerDependencies:
+      - supports-color
+
   mdast-util-from-markdown@2.0.3:
     dependencies:
       '@types/mdast': 4.0.4
@@ -4674,6 +5781,10 @@ snapshots:
       unist-util-visit: 5.1.0
       zwitch: 2.0.4
 
+  mdast-util-to-string@3.2.0:
+    dependencies:
+      '@types/mdast': 3.0.15
+
   mdast-util-to-string@4.0.0:
     dependencies:
       '@types/mdast': 4.0.4
@@ -4682,6 +5793,50 @@ snapshots:
 
   mdn-data@2.27.1: {}
 
+  mermaid@10.9.5:
+    dependencies:
+      '@braintree/sanitize-url': 6.0.4
+      '@types/d3-scale': 4.0.9
+      '@types/d3-scale-chromatic': 3.1.0
+      cytoscape: 3.33.2
+      cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2)
+      d3: 7.9.0
+      d3-sankey: 0.12.3
+      dagre-d3-es: 7.0.13
+      dayjs: 1.11.20
+      dompurify: 3.4.1
+      elkjs: 0.9.3
+      katex: 0.16.45
+      khroma: 2.1.0
+      lodash-es: 4.18.1
+      mdast-util-from-markdown: 1.3.1
+      non-layered-tidy-tree-layout: 2.0.2
+      stylis: 4.4.0
+      ts-dedent: 2.2.0
+      uuid: 9.0.1
+      web-worker: 1.5.0
+    transitivePeerDependencies:
+      - supports-color
+
+  micromark-core-commonmark@1.1.0:
+    dependencies:
+      decode-named-character-reference: 1.3.0
+      micromark-factory-destination: 1.1.0
+      micromark-factory-label: 1.1.0
+      micromark-factory-space: 1.1.0
+      micromark-factory-title: 1.1.0
+      micromark-factory-whitespace: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-chunked: 1.1.0
+      micromark-util-classify-character: 1.1.0
+      micromark-util-html-tag-name: 1.2.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-resolve-all: 1.1.0
+      micromark-util-subtokenize: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+
   micromark-core-commonmark@2.0.3:
     dependencies:
       decode-named-character-reference: 1.3.0
@@ -4759,12 +5914,25 @@ snapshots:
       micromark-util-combine-extensions: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-factory-destination@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-factory-destination@2.0.1:
     dependencies:
       micromark-util-character: 2.1.1
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-factory-label@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+
   micromark-factory-label@2.0.1:
     dependencies:
       devlop: 1.1.0
@@ -4772,11 +5940,23 @@ snapshots:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-factory-space@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-types: 1.1.0
+
   micromark-factory-space@2.0.1:
     dependencies:
       micromark-util-character: 2.1.1
       micromark-util-types: 2.0.2
 
+  micromark-factory-title@1.1.0:
+    dependencies:
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-factory-title@2.0.1:
     dependencies:
       micromark-factory-space: 2.0.1
@@ -4784,6 +5964,13 @@ snapshots:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-factory-whitespace@1.1.0:
+    dependencies:
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-factory-whitespace@2.0.1:
     dependencies:
       micromark-factory-space: 2.0.1
@@ -4791,30 +5978,61 @@ snapshots:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-util-character@1.2.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-util-character@2.1.1:
     dependencies:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-util-chunked@1.1.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
+
   micromark-util-chunked@2.0.1:
     dependencies:
       micromark-util-symbol: 2.0.1
 
+  micromark-util-classify-character@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-util-classify-character@2.0.1:
     dependencies:
       micromark-util-character: 2.1.1
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-util-combine-extensions@1.1.0:
+    dependencies:
+      micromark-util-chunked: 1.1.0
+      micromark-util-types: 1.1.0
+
   micromark-util-combine-extensions@2.0.1:
     dependencies:
       micromark-util-chunked: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-util-decode-numeric-character-reference@1.1.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
+
   micromark-util-decode-numeric-character-reference@2.0.2:
     dependencies:
       micromark-util-symbol: 2.0.1
 
+  micromark-util-decode-string@1.1.0:
+    dependencies:
+      decode-named-character-reference: 1.3.0
+      micromark-util-character: 1.2.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-symbol: 1.1.0
+
   micromark-util-decode-string@2.0.1:
     dependencies:
       decode-named-character-reference: 1.3.0
@@ -4822,24 +6040,49 @@ snapshots:
       micromark-util-decode-numeric-character-reference: 2.0.2
       micromark-util-symbol: 2.0.1
 
+  micromark-util-encode@1.1.0: {}
+
   micromark-util-encode@2.0.1: {}
 
+  micromark-util-html-tag-name@1.2.0: {}
+
   micromark-util-html-tag-name@2.0.1: {}
 
+  micromark-util-normalize-identifier@1.1.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
+
   micromark-util-normalize-identifier@2.0.1:
     dependencies:
       micromark-util-symbol: 2.0.1
 
+  micromark-util-resolve-all@1.1.0:
+    dependencies:
+      micromark-util-types: 1.1.0
+
   micromark-util-resolve-all@2.0.1:
     dependencies:
       micromark-util-types: 2.0.2
 
+  micromark-util-sanitize-uri@1.2.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-encode: 1.1.0
+      micromark-util-symbol: 1.1.0
+
   micromark-util-sanitize-uri@2.0.1:
     dependencies:
       micromark-util-character: 2.1.1
       micromark-util-encode: 2.0.1
       micromark-util-symbol: 2.0.1
 
+  micromark-util-subtokenize@1.1.0:
+    dependencies:
+      micromark-util-chunked: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+
   micromark-util-subtokenize@2.1.0:
     dependencies:
       devlop: 1.1.0
@@ -4847,10 +6090,36 @@ snapshots:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-util-symbol@1.1.0: {}
+
   micromark-util-symbol@2.0.1: {}
 
+  micromark-util-types@1.1.0: {}
+
   micromark-util-types@2.0.2: {}
 
+  micromark@3.2.0:
+    dependencies:
+      '@types/debug': 4.1.12
+      debug: 4.4.3
+      decode-named-character-reference: 1.3.0
+      micromark-core-commonmark: 1.1.0
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-chunked: 1.1.0
+      micromark-util-combine-extensions: 1.1.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-encode: 1.1.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-resolve-all: 1.1.0
+      micromark-util-sanitize-uri: 1.2.0
+      micromark-util-subtokenize: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+    transitivePeerDependencies:
+      - supports-color
+
   micromark@4.0.2:
     dependencies:
       '@types/debug': 4.1.12
@@ -4873,6 +6142,14 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  mime-db@1.52.0: {}
+
+  mime-types@2.1.35:
+    dependencies:
+      mime-db: 1.52.0
+
+  min-indent@1.0.1: {}
+
   minimatch@10.2.4:
     dependencies:
       brace-expansion: 5.0.5
@@ -4883,6 +6160,8 @@ snapshots:
 
   minipass@7.1.3: {}
 
+  mri@1.2.0: {}
+
   mrmime@2.0.1: {}
 
   ms@2.1.3: {}
@@ -4905,12 +6184,16 @@ snapshots:
 
   node-releases@2.0.37: {}
 
+  non-layered-tidy-tree-layout@2.0.2: {}
+
   normalize-path@3.0.0: {}
 
   nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0
 
+  nwsapi@2.2.23: {}
+
   obug@2.1.1: {}
 
   ofetch@1.5.1:
@@ -4944,6 +6227,8 @@ snapshots:
 
   package-manager-detector@1.6.0: {}
 
+  pako@2.1.0: {}
+
   parse-entities@4.0.2:
     dependencies:
       '@types/unist': 2.0.11
@@ -4994,10 +6279,18 @@ snapshots:
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
+  pretty-format@27.5.1:
+    dependencies:
+      ansi-regex: 5.0.1
+      ansi-styles: 5.2.0
+      react-is: 17.0.2
+
   prismjs@1.30.0: {}
 
   property-information@7.1.0: {}
 
+  punycode@2.3.1: {}
+
   radix3@1.1.2: {}
 
   react-dom@19.2.4(react@19.2.4):
@@ -5005,6 +6298,8 @@ snapshots:
       react: 19.2.4
       scheduler: 0.27.0
 
+  react-is@17.0.2: {}
+
   react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4):
     dependencies:
       '@types/hast': 3.0.4
@@ -5029,6 +6324,11 @@ snapshots:
 
   readdirp@5.0.0: {}
 
+  redent@3.0.0:
+    dependencies:
+      indent-string: 4.0.0
+      strip-indent: 3.0.0
+
   regex-recursion@6.0.2:
     dependencies:
       regex-utilities: 2.3.0
@@ -5130,6 +6430,8 @@ snapshots:
       retext-stringify: 4.0.0
       unified: 11.0.5
 
+  robust-predicates@3.0.3: {}
+
   rollup@4.59.0:
     dependencies:
       '@types/estree': 1.0.8
@@ -5161,8 +6463,24 @@ snapshots:
       '@rollup/rollup-win32-x64-msvc': 4.59.0
       fsevents: 2.3.3
 
+  rrweb-cssom@0.7.1: {}
+
+  rrweb-cssom@0.8.0: {}
+
+  rw@1.3.3: {}
+
+  sade@1.8.1:
+    dependencies:
+      mri: 1.2.0
+
+  safer-buffer@2.1.2: {}
+
   sax@1.5.0: {}
 
+  saxes@6.0.0:
+    dependencies:
+      xmlchars: 2.2.0
+
   scheduler@0.27.0: {}
 
   semver@6.3.1: {}
@@ -5222,6 +6540,12 @@ snapshots:
 
   signal-exit@4.1.0: {}
 
+  sirv@3.0.2:
+    dependencies:
+      '@polka/url': 1.0.0-next.29
+      mrmime: 2.0.1
+      totalist: 3.0.1
+
   sisteransi@1.0.5: {}
 
   smol-toml@1.6.0: {}
@@ -5259,6 +6583,10 @@ snapshots:
     dependencies:
       ansi-regex: 6.2.2
 
+  strip-indent@3.0.0:
+    dependencies:
+      min-indent: 1.0.1
+
   strip-literal@3.1.0:
     dependencies:
       js-tokens: 9.0.1
@@ -5271,6 +6599,8 @@ snapshots:
     dependencies:
       inline-style-parser: 0.2.7
 
+  stylis@4.4.0: {}
+
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -5285,6 +6615,8 @@ snapshots:
       picocolors: 1.1.1
       sax: 1.5.0
 
+  symbol-tree@3.2.4: {}
+
   tailwindcss@4.2.1: {}
 
   tapable@2.3.0: {}
@@ -5316,6 +6648,22 @@ snapshots:
 
   tinyspy@4.0.4: {}
 
+  tldts-core@6.1.86: {}
+
+  tldts@6.1.86:
+    dependencies:
+      tldts-core: 6.1.86
+
+  totalist@3.0.1: {}
+
+  tough-cookie@5.1.2:
+    dependencies:
+      tldts: 6.1.86
+
+  tr46@5.1.1:
+    dependencies:
+      punycode: 2.3.1
+
   tree-sitter-c-sharp@0.23.5:
     dependencies:
       node-addon-api: 8.6.0
@@ -5382,6 +6730,8 @@ snapshots:
 
   trough@2.2.0: {}
 
+  ts-dedent@2.2.0: {}
+
   tsconfck@3.1.6(typescript@5.9.3):
     optionalDependencies:
       typescript: 5.9.3
@@ -5440,6 +6790,10 @@ snapshots:
       '@types/unist': 3.0.3
       unist-util-visit: 5.1.0
 
+  unist-util-stringify-position@3.0.3:
+    dependencies:
+      '@types/unist': 2.0.11
+
   unist-util-stringify-position@4.0.0:
     dependencies:
       '@types/unist': 3.0.3
@@ -5480,6 +6834,15 @@ snapshots:
     dependencies:
       react: 19.2.4
 
+  uuid@9.0.1: {}
+
+  uvu@0.5.6:
+    dependencies:
+      dequal: 2.0.3
+      diff: 5.2.2
+      kleur: 4.1.5
+      sade: 1.8.1
+
   vfile-location@5.0.3:
     dependencies:
       '@types/unist': 3.0.3
@@ -5601,7 +6964,7 @@ snapshots:
     optionalDependencies:
       vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
 
-  vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
+  vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3):
     dependencies:
       '@types/chai': 5.2.3
       '@vitest/expect': 3.2.4
@@ -5629,6 +6992,8 @@ snapshots:
     optionalDependencies:
       '@types/debug': 4.1.12
       '@types/node': 22.19.15
+      '@vitest/ui': 3.2.4(vitest@3.2.4)
+      jsdom: 25.0.1
     transitivePeerDependencies:
       - jiti
       - less
@@ -5643,7 +7008,7 @@ snapshots:
       - tsx
       - yaml
 
-  vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
+  vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3):
     dependencies:
       '@types/chai': 5.2.3
       '@vitest/expect': 3.2.4
@@ -5671,6 +7036,8 @@ snapshots:
     optionalDependencies:
       '@types/debug': 4.1.12
       '@types/node': 25.5.0
+      '@vitest/ui': 3.2.4(vitest@3.2.4)
+      jsdom: 25.0.1
     transitivePeerDependencies:
       - jiti
       - less
@@ -5685,10 +7052,29 @@ snapshots:
       - tsx
       - yaml
 
+  w3c-xmlserializer@5.0.0:
+    dependencies:
+      xml-name-validator: 5.0.0
+
   web-namespaces@2.0.1: {}
 
   web-tree-sitter@0.26.6: {}
 
+  web-worker@1.5.0: {}
+
+  webidl-conversions@7.0.0: {}
+
+  whatwg-encoding@3.1.1:
+    dependencies:
+      iconv-lite: 0.6.3
+
+  whatwg-mimetype@4.0.0: {}
+
+  whatwg-url@14.2.0:
+    dependencies:
+      tr46: 5.1.1
+      webidl-conversions: 7.0.0
+
   which-pm-runs@1.1.0: {}
 
   which@2.0.2:
@@ -5712,6 +7098,12 @@ snapshots:
       string-width: 5.1.2
       strip-ansi: 7.2.0
 
+  ws@8.20.0: {}
+
+  xml-name-validator@5.0.0: {}
+
+  xmlchars@2.2.0: {}
+
   xxhash-wasm@1.1.0: {}
 
   yallist@3.1.1: {}
diff --git a/understand-anything-plugin/agents/assemble-reviewer.md b/understand-anything-plugin/agents/assemble-reviewer.md
index ff611e48b..0b574b80a 100644
--- a/understand-anything-plugin/agents/assemble-reviewer.md
+++ b/understand-anything-plugin/agents/assemble-reviewer.md
@@ -67,7 +67,31 @@ The merge script combines what each batch produced independently. Batches don't
 - If an edge is missing between two file nodes that should be connected, add it with `type: "imports"`, `direction: "forward"`, `weight: 0.7`.
 - Do NOT add speculative edges — only add edges that are backed by `$IMPORT_MAP` data.
 
-### Step 4 — Write results
+### Step 4 — Mermaid dry-run validation
+
+After schema validation, for every node in the assembled graph with a non-empty `domainMeta.mermaid` field, dry-run-parse the source to ensure it is valid Mermaid syntax.
+
+Implementation:
+
+1. Collect all nodes with `domainMeta.mermaid` into a JSONL stream where each line is `{"id": "", "source": ""}`.
+2. Pipe the stream to `node understand-anything-plugin/scripts/validate-mermaid.mjs`.
+3. Read the JSONL output. For every line where `ok === false`:
+   - Remove the `mermaid` field from that node in the assembled graph (do NOT abort the whole assembly).
+   - Append a log line `[mermaid-invalid] : ` to `.understand-anything/intermediate/mermaid-errors.log`.
+4. Continue with remaining reviewer steps.
+
+Example Bash invocation:
+
+```bash
+jq -c '.nodes[] | select(.domainMeta.mermaid != null) | {id: .id, source: .domainMeta.mermaid}' \
+  .understand-anything/intermediate/assembled-graph.json \
+  | node understand-anything-plugin/scripts/validate-mermaid.mjs \
+  > .understand-anything/intermediate/mermaid-validation.jsonl
+```
+
+Then process `mermaid-validation.jsonl` with a follow-up jq/node step to strip invalid entries and produce `mermaid-errors.log`.
+
+### Step 5 — Write results
 
 1. Apply all fixes directly to `assembled-graph.json`.
 2. Write a summary to the review output path provided in your prompt:
diff --git a/understand-anything-plugin/agents/domain-analyzer.md b/understand-anything-plugin/agents/domain-analyzer.md
index bd25bb82e..8caa6a042 100644
--- a/understand-anything-plugin/agents/domain-analyzer.md
+++ b/understand-anything-plugin/agents/domain-analyzer.md
@@ -57,7 +57,8 @@ Produce a JSON object with this exact structure:
       "domainMeta": {
         "entities": [""],
         "businessRules": [""],
-        "crossDomainInteractions": [""]
+        "crossDomainInteractions": [""],
+        "mermaid": ""
       }
     },
     {
@@ -95,6 +96,30 @@ Produce a JSON object with this exact structure:
 
 **Note:** `layers` and `tour` are intentionally empty for domain graphs. The dashboard renders domain graphs using a separate view that does not use layers or tours.
 
+### Mermaid flowchart generation
+
+For each `domain` node you produce, also emit `domainMeta.mermaid` — a Mermaid `flowchart TD` source string representing the internal business logic of that domain. Rules:
+
+- Each logical step or component in the domain is a flowchart node (e.g. "Cart Retrieval", "Currency Convert", "Payment Charge").
+- Edges represent control or data flow between steps.
+- **When an edge corresponds to an API call, label the arrow with the API** using `|METHOD /path|` syntax (e.g. `A -->|POST /charge| B`).
+- Keep node labels concise (≤40 characters); use `[...]` for process nodes and `{...}` for decisions.
+- Target 5–15 nodes per domain; if more, group related steps into Mermaid subgraphs.
+- Output valid Mermaid syntax — it will be dry-run parsed by `scripts/validate-mermaid.mjs` before commit; invalid sources will be stripped.
+
+Example:
+
+```
+flowchart TD
+  A[Cart Retrieval] -->|GET /cart| B[Product Lookup]
+  B -->|GET /products/:id| C[Currency Convert]
+  C -->|POST /currency/convert| D[Shipping Quote]
+  D -->|POST /shipping/quote| E[Payment Charge]
+  E -->|POST /charge| F[Email Confirmation]
+```
+
+Emit the source as a JSON string (newlines escaped as `\n`). Place under the domain node's `domainMeta.mermaid`. Do NOT emit for `flow` or `step` nodes — only for `domain` nodes.
+
 ## Rules
 
 1. **flow_step weight encodes order**: Use fractional weights within 0-1 range. For N steps: first = 1/N rounded to 1 decimal, second = 2/N, etc. Example for 5 steps: 0.1, 0.2, 0.3, 0.4, 0.5. For 15 steps: 0.1, 0.1, 0.1, ... (use increments of `round(1/N, 1)`, minimum 0.1). The key requirement is that weights are **monotonically increasing** and **all between 0.0 and 1.0 inclusive**.
diff --git a/understand-anything-plugin/package.json b/understand-anything-plugin/package.json
index ff4f76b5a..0772ef8e9 100644
--- a/understand-anything-plugin/package.json
+++ b/understand-anything-plugin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@understand-anything/skill",
-  "version": "2.3.1",
+  "version": "2.4.0",
   "type": "module",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
@@ -9,7 +9,8 @@
     "test": "vitest run"
   },
   "dependencies": {
-    "@understand-anything/core": "workspace:*"
+    "@understand-anything/core": "workspace:*",
+    "mermaid": "^10.9.0"
   },
   "devDependencies": {
     "@types/node": "^22.0.0",
diff --git a/understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts b/understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts
new file mode 100644
index 000000000..33c9db1eb
--- /dev/null
+++ b/understand-anything-plugin/packages/core/src/__tests__/schema.mermaid.test.ts
@@ -0,0 +1,63 @@
+import { describe, it, expect } from "vitest";
+import { validateGraph, sanitizeGraph } from "../schema.js";
+import type { KnowledgeGraph } from "../types.js";
+
+function graphWith(domainMeta: Record) {
+  return {
+    version: "1.0.0",
+    project: {
+      name: "test-project",
+      languages: ["typescript"],
+      frameworks: [],
+      description: "A test project",
+      analyzedAt: "2026-01-01T00:00:00.000Z",
+      gitCommitHash: "abc123",
+    },
+    nodes: [
+      {
+        id: "d1",
+        type: "domain",
+        name: "Checkout",
+        summary: "checkout domain",
+        tags: [],
+        complexity: "simple",
+        domainMeta,
+      },
+    ],
+    edges: [],
+    layers: [],
+    tour: [],
+  };
+}
+
+describe("DomainMeta.mermaid schema", () => {
+  it("accepts a valid mermaid source string", () => {
+    const result = validateGraph(
+      graphWith({ mermaid: "flowchart TD\n  A --> B" }),
+    );
+    expect(result.success).toBe(true);
+  });
+
+  it("accepts a graph with no mermaid field", () => {
+    const result = validateGraph(graphWith({ entities: ["Order"] }));
+    expect(result.success).toBe(true);
+  });
+
+  it("rejects an empty mermaid string", () => {
+    const result = validateGraph(graphWith({ mermaid: "" }));
+    expect(result.success).toBe(false);
+  });
+
+  it("rejects a non-string mermaid value", () => {
+    const result = validateGraph(graphWith({ mermaid: 42 as unknown as string }));
+    expect(result.success).toBe(false);
+  });
+});
+
+describe("sanitizeGraph preserves mermaid", () => {
+  it("keeps a valid mermaid source through sanitization", () => {
+    const g = graphWith({ mermaid: "flowchart TD\n  A --> B" });
+    const cleaned = sanitizeGraph(g as never) as unknown as KnowledgeGraph;
+    expect(cleaned.nodes[0].domainMeta?.mermaid).toBe("flowchart TD\n  A --> B");
+  });
+});
diff --git a/understand-anything-plugin/packages/core/src/schema.ts b/understand-anything-plugin/packages/core/src/schema.ts
index cc89e25aa..57f3f21fe 100644
--- a/understand-anything-plugin/packages/core/src/schema.ts
+++ b/understand-anything-plugin/packages/core/src/schema.ts
@@ -356,6 +356,7 @@ const DomainMetaSchema = z.object({
   crossDomainInteractions: z.array(z.string()).optional(),
   entryPoint: z.string().optional(),
   entryType: z.enum(["http", "cli", "event", "cron", "manual"]).optional(),
+  mermaid: z.string().min(1).optional(),
 }).passthrough();
 
 const KnowledgeMetaSchema = z.object({
diff --git a/understand-anything-plugin/packages/core/src/types.ts b/understand-anything-plugin/packages/core/src/types.ts
index 16179268a..92fcd2593 100644
--- a/understand-anything-plugin/packages/core/src/types.ts
+++ b/understand-anything-plugin/packages/core/src/types.ts
@@ -33,6 +33,7 @@ export interface DomainMeta {
   crossDomainInteractions?: string[];
   entryPoint?: string;
   entryType?: "http" | "cli" | "event" | "cron" | "manual";
+  mermaid?: string;
 }
 
 // GraphNode with 21 types: 5 code + 8 non-code + 3 domain + 5 knowledge
diff --git a/understand-anything-plugin/packages/dashboard/package.json b/understand-anything-plugin/packages/dashboard/package.json
index 27020911b..24417679b 100644
--- a/understand-anything-plugin/packages/dashboard/package.json
+++ b/understand-anything-plugin/packages/dashboard/package.json
@@ -7,7 +7,9 @@
     "dev": "vite",
     "build": "tsc -b && vite build",
     "build:demo": "tsc -b && vite build --config vite.config.demo.ts",
-    "preview": "vite preview"
+    "preview": "vite preview",
+    "test": "vitest run",
+    "test:watch": "vitest"
   },
   "dependencies": {
     "@dagrejs/dagre": "^2.0.4",
@@ -16,6 +18,8 @@
     "d3-force": "^3.0.0",
     "devlop": "^1.1.0",
     "hast-util-to-jsx-runtime": "^2.3.6",
+    "mermaid": "^10.9.0",
+    "pako": "^2.1.0",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
     "react-markdown": "^10.1.0",
@@ -23,12 +27,19 @@
   },
   "devDependencies": {
     "@tailwindcss/vite": "^4.0.0",
+    "@testing-library/jest-dom": "^6.6.0",
+    "@testing-library/react": "^16.1.0",
+    "@testing-library/user-event": "^14.5.0",
     "@types/d3-force": "^3.0.10",
+    "@types/pako": "^2.0.3",
     "@types/react": "^19.0.0",
     "@types/react-dom": "^19.0.0",
     "@vitejs/plugin-react": "^4.3.0",
+    "@vitest/ui": "^3.1.0",
+    "jsdom": "^25.0.0",
     "tailwindcss": "^4.0.0",
     "typescript": "^5.7.0",
-    "vite": "^6.0.0"
+    "vite": "^6.0.0",
+    "vitest": "^3.1.0"
   }
 }
diff --git a/understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts b/understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts
new file mode 100644
index 000000000..3dc9fc2f4
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/__tests__/store.mermaid.test.ts
@@ -0,0 +1,56 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { useDashboardStore } from "../store.js";
+
+describe("mermaid popup slice", () => {
+  beforeEach(() => {
+    useDashboardStore.setState({
+      mermaidPopupNodeId: null,
+      mermaidModalOpen: false,
+    });
+  });
+
+  it("opens the popup with a node id", () => {
+    useDashboardStore.getState().openMermaidPopup("d1");
+    expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1");
+  });
+
+  it("closes the popup and the modal", () => {
+    useDashboardStore.setState({
+      mermaidPopupNodeId: "d1",
+      mermaidModalOpen: true,
+    });
+    useDashboardStore.getState().closeMermaidPopup();
+    expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull();
+    expect(useDashboardStore.getState().mermaidModalOpen).toBe(false);
+  });
+
+  it("opens and closes the modal independently", () => {
+    useDashboardStore.setState({ mermaidPopupNodeId: "d1" });
+    useDashboardStore.getState().openMermaidModal();
+    expect(useDashboardStore.getState().mermaidModalOpen).toBe(true);
+    useDashboardStore.getState().closeMermaidModal();
+    expect(useDashboardStore.getState().mermaidModalOpen).toBe(false);
+    expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1");
+  });
+});
+
+describe("mermaid popup closes on view switch", () => {
+  it("clears popup when switching to structural view", () => {
+    useDashboardStore.setState({
+      mermaidPopupNodeId: "d1",
+      mermaidModalOpen: true,
+    });
+    useDashboardStore.getState().setViewMode("structural");
+    expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull();
+    expect(useDashboardStore.getState().mermaidModalOpen).toBe(false);
+  });
+
+  it("clears popup when switching to knowledge view", () => {
+    useDashboardStore.setState({
+      mermaidPopupNodeId: "d1",
+      mermaidModalOpen: false,
+    });
+    useDashboardStore.getState().setViewMode("knowledge");
+    expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull();
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx b/understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx
index 317cbfc76..8473b3d9b 100644
--- a/understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx
+++ b/understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx
@@ -18,6 +18,7 @@ function DomainClusterNode({ data }: NodeProps) {
   const navigateToDomain = useDashboardStore((s) => s.navigateToDomain);
   const selectedNodeId = useDashboardStore((s) => s.selectedNodeId);
   const selectNode = useDashboardStore((s) => s.selectNode);
+  const openMermaidPopup = useDashboardStore((s) => s.openMermaidPopup);
   const isSelected = selectedNodeId === data.domainId;
 
   return (
@@ -27,7 +28,10 @@ function DomainClusterNode({ data }: NodeProps) {
           ? "border-accent bg-accent/10 shadow-lg shadow-accent/10"
           : "border-accent/40 bg-surface hover:border-accent/70"
       }`}
-      onClick={() => selectNode(data.domainId)}
+      onClick={() => {
+        selectNode(data.domainId);
+        openMermaidPopup(data.domainId);
+      }}
       onDoubleClick={() => navigateToDomain(data.domainId)}
     >
       
diff --git a/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx
index 6426cc2cc..3e68f2294 100644
--- a/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx
+++ b/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx
@@ -10,6 +10,8 @@ import {
 import type { Edge, Node } from "@xyflow/react";
 import "@xyflow/react/dist/style.css";
 
+import { MermaidFlowchartPopup } from "./MermaidFlowchartPopup.js";
+import { MermaidFlowchartModal } from "./MermaidFlowchartModal.js";
 import DomainClusterNode from "./DomainClusterNode";
 import type { DomainClusterFlowNode } from "./DomainClusterNode";
 import FlowNode from "./FlowNode";
@@ -219,6 +221,8 @@ function DomainGraphViewInner() {
           className="!bg-surface !border !border-border-subtle"
         />
       
+      
+      
     
); } diff --git a/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx b/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx new file mode 100644 index 000000000..85b93c6f7 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartModal.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef } from "react"; +import { createPortal } from "react-dom"; +import pako from "pako"; +import { useDashboardStore } from "../store.js"; +import { MermaidRenderer } from "./MermaidRenderer.js"; + +function toPakoBase64(source: string): string { + const data = pako.deflate(new TextEncoder().encode(source), { level: 9 }); + let binary = ""; + for (const byte of data) binary += String.fromCharCode(byte); + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} + +function buildLiveUrl(source: string): string { + const state = { + code: source, + mermaid: { theme: "dark" }, + autoSync: true, + updateDiagram: true, + }; + const encoded = toPakoBase64(JSON.stringify(state)); + return `https://mermaid.live/edit#pako:${encoded}`; +} + +export function MermaidFlowchartModal() { + const open = useDashboardStore((s) => s.mermaidModalOpen); + const nodeId = useDashboardStore((s) => s.mermaidPopupNodeId); + const graph = useDashboardStore((s) => s.domainGraph); + const close = useDashboardStore((s) => s.closeMermaidModal); + const closeButtonRef = useRef(null); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") close(); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, close]); + + useEffect(() => { + if (!open) return; + const previous = document.activeElement as HTMLElement | null; + closeButtonRef.current?.focus(); + return () => { + previous?.focus(); + }; + }, [open]); + + if (!open || !nodeId || !graph) return null; + const node = graph.nodes.find((n) => n.id === nodeId); + if (!node?.domainMeta?.mermaid) return null; + + const source = node.domainMeta.mermaid; + + return createPortal( +
+
+
+
+ + {node.name} + +
+ + + Open in Mermaid Live + + +
+
+
+ +
+
+
, + document.body, + ); +} + +function CloseIcon() { + return ( + + + + + ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx b/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx new file mode 100644 index 000000000..aa0e74c7f --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/MermaidFlowchartPopup.tsx @@ -0,0 +1,135 @@ +import { useEffect, useRef, useState } from "react"; +import { useDashboardStore } from "../store.js"; +import { MermaidRenderer } from "./MermaidRenderer.js"; +import { useMermaidPopupPosition } from "../hooks/useMermaidPopupPosition.js"; + +const POPUP_W = 480; +const POPUP_H = 260; + +export function MermaidFlowchartPopup() { + const nodeId = useDashboardStore((s) => s.mermaidPopupNodeId); + const graph = useDashboardStore((s) => s.domainGraph); + const closePopup = useDashboardStore((s) => s.closeMermaidPopup); + const openModal = useDashboardStore((s) => s.openMermaidModal); + + const containerRef = useRef(null); + const [bounds, setBounds] = useState({ width: 0, height: 0, left: 0, top: 0 }); + + useEffect(() => { + const el = containerRef.current?.parentElement; + if (!el) return; + const update = () => { + const rect = el.getBoundingClientRect(); + setBounds({ + width: el.clientWidth, + height: el.clientHeight, + left: rect.left, + top: rect.top, + }); + }; + update(); + const ro = new ResizeObserver(update); + ro.observe(el); + window.addEventListener("scroll", update, true); + window.addEventListener("resize", update); + return () => { + ro.disconnect(); + window.removeEventListener("scroll", update, true); + window.removeEventListener("resize", update); + }; + }, [nodeId]); + + const position = useMermaidPopupPosition(POPUP_W, POPUP_H, bounds); + + if (!nodeId || !graph) return null; + const node = graph.nodes.find((n) => n.id === nodeId); + if (!node) return null; + const mermaidSource = node.domainMeta?.mermaid; + + return ( +
+
+ + {node.name} + +
+ + +
+
+
+ {mermaidSource ? ( + + ) : ( + + )} +
+
+ ); +} + +function EmptyState() { + return ( +
+

+ No detailed diagram yet +

+

+ Re-run{" "} + + /understand --full + {" "} + to generate the flowchart. +

+
+ ); +} + +function EnlargeIcon() { + return ( + + + + + + + ); +} + +function CloseIcon() { + return ( + + + + + ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx b/understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx new file mode 100644 index 000000000..e4e93ce9e --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/MermaidRenderer.tsx @@ -0,0 +1,165 @@ +import { useEffect, useRef, useState } from "react"; +import type { ReactNode } from "react"; +import { getMermaidTheme } from "../themes/mermaid-theme.js"; +import { onThemeChange } from "../themes/theme-engine.js"; + +function PanZoomScroll({ children }: { children: ReactNode }) { + const ref = useRef(null); + const drag = useRef<{ startX: number; startY: number; scrollLeft: number; scrollTop: number } | null>(null); + + const handleMouseDown = (e: React.MouseEvent) => { + if (e.button !== 0) return; + const target = e.target as HTMLElement; + if (target.closest("a, button, input")) return; + const el = ref.current; + if (!el) return; + drag.current = { + startX: e.clientX, + startY: e.clientY, + scrollLeft: el.scrollLeft, + scrollTop: el.scrollTop, + }; + el.style.cursor = "grabbing"; + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (!drag.current || !ref.current) return; + const dx = e.clientX - drag.current.startX; + const dy = e.clientY - drag.current.startY; + ref.current.scrollLeft = drag.current.scrollLeft - dx; + ref.current.scrollTop = drag.current.scrollTop - dy; + }; + + const endDrag = () => { + if (!drag.current) return; + drag.current = null; + if (ref.current) ref.current.style.cursor = "grab"; + }; + + return ( +
+ {children} +
+ ); +} + +type RenderState = + | { kind: "loading" } + | { kind: "ok"; svg: string } + | { kind: "err"; msg: string } + | { kind: "chunk-err" }; + +interface Props { + source: string; +} + +export function MermaidRenderer({ source }: Props) { + const [state, setState] = useState({ kind: "loading" }); + const [themeVersion, setThemeVersion] = useState(0); + + useEffect(() => onThemeChange(() => setThemeVersion((v) => v + 1)), []); + + useEffect(() => { + let cancelled = false; + (async () => { + setState({ kind: "loading" }); + let mermaid; + try { + mermaid = (await import("mermaid")).default; + } catch { + if (!cancelled) setState({ kind: "chunk-err" }); + return; + } + mermaid.initialize({ + startOnLoad: false, + theme: "base", + themeVariables: getMermaidTheme() as unknown as Record, + flowchart: { + padding: 8, + nodeSpacing: 32, + rankSpacing: 40, + curve: "basis", + htmlLabels: true, + useMaxWidth: false, + }, + }); + try { + const id = `m-${crypto.randomUUID()}`; + const { svg } = await mermaid.render(id, source); + if (!cancelled) setState({ kind: "ok", svg }); + } catch (err) { + if (!cancelled) setState({ kind: "err", msg: String((err as Error)?.message ?? err) }); + console.warn("[mermaid-parse-error]", err); + } + })(); + return () => { + cancelled = true; + }; + }, [source, themeVersion]); + + if (state.kind === "loading") { + return ( +
+ Loading diagram… +
+ ); + } + + if (state.kind === "chunk-err") { + return ( +
+

+ ⚠ Failed to load diagram library +

+

+ Check your network and retry. +

+ +
+ ); + } + + if (state.kind === "err") { + return ( +
+

+ ⚠ Diagram render failed. Showing source instead. +

+
+          {source}
+        
+ +
+ ); + } + + return ( + +
+ + ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx new file mode 100644 index 000000000..f8da5d776 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartModal.test.tsx @@ -0,0 +1,100 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidFlowchartModal } from "../MermaidFlowchartModal.js"; +import { useDashboardStore } from "../../store.js"; + +vi.mock("mermaid", () => ({ + default: { + initialize: vi.fn(), + render: vi.fn(async () => ({ svg: "" })), + }, +})); + +function seed() { + useDashboardStore.setState({ + domainGraph: { + version: "1.0.0", + project: { + name: "test", + languages: [], + frameworks: [], + description: "", + analyzedAt: "", + gitCommitHash: "", + }, + nodes: [ + { + id: "d1", + type: "domain", + name: "Checkout and Ordering", + summary: "", + tags: [], + complexity: "simple", + domainMeta: { mermaid: "flowchart TD\n A --> B" }, + }, + ], + edges: [], + layers: [], + tour: [], + }, + mermaidPopupNodeId: "d1", + mermaidModalOpen: true, + }); +} + +describe("MermaidFlowchartModal", () => { + beforeEach(() => seed()); + + it("closes on Esc", async () => { + render(); + await userEvent.keyboard("{Escape}"); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("closes on X button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /close/i })); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(false); + }); + + it("does NOT close on backdrop click", async () => { + render(); + await userEvent.click(screen.getByTestId("mermaid-modal-backdrop")); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(true); + }); + + it("copies source via Copy button", async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, "clipboard", { + value: { writeText }, + writable: true, + configurable: true, + }); + render(); + await userEvent.click(screen.getByRole("button", { name: /copy source/i })); + expect(writeText).toHaveBeenCalledWith("flowchart TD\n A --> B"); + }); + + it("generates an Open-in-Mermaid-Live href with pako-encoded source", () => { + render(); + const link = screen.getByRole("link", { name: /open in mermaid live/i }); + const href = link.getAttribute("href") ?? ""; + expect(href.startsWith("https://mermaid.live/edit#pako:")).toBe(true); + expect(href.length).toBeGreaterThan(40); + }); + + it("renders nothing when modal is closed", () => { + useDashboardStore.setState({ mermaidModalOpen: false }); + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it("sets aria-modal and focuses close button when opened", () => { + render(); + const dialog = screen.getByRole("dialog"); + expect(dialog).toHaveAttribute("aria-modal", "true"); + // Focus may already be on close button after useEffect runs + expect(screen.getByRole("button", { name: /close/i })).toBe(document.activeElement); + }); +}); diff --git a/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx new file mode 100644 index 000000000..c8a42c6a0 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidFlowchartPopup.test.tsx @@ -0,0 +1,93 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidFlowchartPopup } from "../MermaidFlowchartPopup.js"; +import { useDashboardStore } from "../../store.js"; + +vi.mock("mermaid", () => ({ + default: { + initialize: vi.fn(), + render: vi.fn(async () => ({ svg: "" })), + }, +})); + +vi.mock("@xyflow/react", () => ({ + useReactFlow: () => ({ + getNodes: () => [], + flowToScreenPosition: (p: { x: number; y: number }) => p, + }), + useStore: () => ({ x: 0, y: 0, zoom: 1 }), +})); + +function seedGraph(mermaidValue?: string) { + useDashboardStore.setState({ + domainGraph: { + version: "1.0.0", + project: { + name: "test", + languages: [], + frameworks: [], + description: "", + analyzedAt: "", + gitCommitHash: "", + }, + nodes: [ + { + id: "d1", + type: "domain", + name: "Checkout and Ordering", + summary: "", + tags: [], + complexity: "simple", + domainMeta: mermaidValue ? { mermaid: mermaidValue } : {}, + }, + ], + edges: [], + layers: [], + tour: [], + }, + mermaidPopupNodeId: "d1", + mermaidModalOpen: false, + }); +} + +describe("MermaidFlowchartPopup", () => { + beforeEach(() => { + seedGraph("flowchart TD\n A --> B"); + }); + + it("shows empty state and disables enlarge when no mermaid field", () => { + seedGraph(undefined); + render(); + expect(screen.getByText(/No detailed diagram/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /enlarge/i })).toBeDisabled(); + }); + + it("shows node name in header", () => { + render(); + expect(screen.getByText("Checkout and Ordering")).toBeInTheDocument(); + }); + + it("closes on X button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /close/i })); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBeNull(); + }); + + it("opens modal on enlarge button", async () => { + render(); + await userEvent.click(screen.getByRole("button", { name: /enlarge/i })); + expect(useDashboardStore.getState().mermaidModalOpen).toBe(true); + }); + + it("Esc does NOT close the popup", async () => { + render(); + await userEvent.keyboard("{Escape}"); + expect(useDashboardStore.getState().mermaidPopupNodeId).toBe("d1"); + }); + + it("sets aria-modal on popup dialog", () => { + render(); + expect(screen.getByRole("dialog")).toHaveAttribute("aria-modal", "true"); + }); +}); diff --git a/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx new file mode 100644 index 000000000..7dbf0c198 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/__tests__/MermaidRenderer.test.tsx @@ -0,0 +1,65 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MermaidRenderer } from "../MermaidRenderer.js"; + +vi.mock("mermaid", () => { + return { + default: { + initialize: vi.fn(), + render: vi.fn(async (id: string, source: string) => { + if (source.includes("INVALID_SYNTAX")) throw new Error("Parse error near INVALID_SYNTAX"); + return { svg: `` }; + }), + }, + }; +}); + +describe("MermaidRenderer", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders SVG for a valid source", async () => { + render( B"} />); + await waitFor(() => + expect(document.querySelector("svg[data-id]")).toBeInTheDocument(), + ); + }); + + it("falls back to
 on parse error with copy button", async () => {
+    render();
+    await waitFor(() =>
+      expect(screen.getByText(/Diagram render failed/i)).toBeInTheDocument(),
+    );
+    expect(screen.getByRole("button", { name: /copy source/i })).toBeEnabled();
+    expect(screen.getByText(/INVALID_SYNTAX/)).toBeInTheDocument();
+  });
+
+  it("copies source to clipboard on click", async () => {
+    const writeText = vi.fn().mockResolvedValue(undefined);
+    Object.defineProperty(navigator, "clipboard", {
+      value: { writeText },
+      writable: true,
+      configurable: true,
+    });
+    render();
+    await waitFor(() => screen.getByRole("button", { name: /copy source/i }));
+    await userEvent.click(screen.getByRole("button", { name: /copy source/i }));
+    expect(writeText).toHaveBeenCalledWith("flowchart TD\n  INVALID_SYNTAX");
+  });
+
+  it("shows retry UI when the mermaid chunk fails to load", async () => {
+    vi.resetModules();
+    vi.doMock("mermaid", () => {
+      throw new Error("ChunkLoadError: expected failure");
+    });
+    const { MermaidRenderer: FreshRenderer } = await import("../MermaidRenderer.js");
+    render();
+    await waitFor(() =>
+      expect(screen.getByText(/Failed to load diagram library/i)).toBeInTheDocument(),
+    );
+    expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument();
+    vi.doUnmock("mermaid");
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.collision.test.ts b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.collision.test.ts
new file mode 100644
index 000000000..f0eb282a2
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.collision.test.ts
@@ -0,0 +1,31 @@
+import { describe, it, expect, vi } from "vitest";
+import { renderHook } from "@testing-library/react";
+
+// A node sits at the right portion of candidate 0's rect (blocking candidate 0).
+// It does NOT block candidate 1 (x: 280..760) because the node starts at x=761.
+//
+// Candidate 0 popup rect: left=360, right=840, top=524, bottom=784
+//   Node (761..840, 524..784): popup.right(840) >= node.left(761) → INTERSECT → blocked
+//
+// Candidate 1 popup rect: left=280, right=760, top=524, bottom=784
+//   Node (761..840, 524..784): popup.right(760) < node.left(761) → NO intersect → free
+vi.mock("@xyflow/react", () => ({
+  useReactFlow: () => ({
+    getNodes: () => [
+      { id: "n1", position: { x: 761, y: 524 }, width: 79, height: 260 },
+    ],
+    flowToScreenPosition: (p: { x: number; y: number }) => p,
+  }),
+  useStore: () => ({ x: 0, y: 0, zoom: 1 }),
+}));
+
+import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js";
+
+describe("useMermaidPopupPosition · collision avoidance", () => {
+  it("shifts left by 80px when bottom-center is occupied", () => {
+    const { result } = renderHook(() =>
+      useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }),
+    );
+    expect(result.current.x).toBe((1200 - 480) / 2 - 80);
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.empty.test.ts b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.empty.test.ts
new file mode 100644
index 000000000..926583333
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.empty.test.ts
@@ -0,0 +1,22 @@
+import { describe, it, expect, vi } from "vitest";
+import { renderHook } from "@testing-library/react";
+
+vi.mock("@xyflow/react", () => ({
+  useReactFlow: () => ({
+    getNodes: () => [],
+    flowToScreenPosition: (p: { x: number; y: number }) => p,
+  }),
+  useStore: () => ({ x: 0, y: 0, zoom: 1 }),
+}));
+
+import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js";
+
+describe("useMermaidPopupPosition · no nodes", () => {
+  it("returns bottom-center when no nodes present", () => {
+    const { result } = renderHook(() =>
+      useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }),
+    );
+    expect(result.current.x).toBe((1200 - 480) / 2);
+    expect(result.current.y).toBe(16);
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.fallback.test.ts b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.fallback.test.ts
new file mode 100644
index 000000000..dcd707698
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.fallback.test.ts
@@ -0,0 +1,25 @@
+import { describe, it, expect, vi } from "vitest";
+import { renderHook } from "@testing-library/react";
+
+// One enormous node covers the entire container — every candidate collides.
+vi.mock("@xyflow/react", () => ({
+  useReactFlow: () => ({
+    getNodes: () => [
+      { id: "n1", position: { x: 0, y: 0 }, width: 1200, height: 800 },
+    ],
+    flowToScreenPosition: (p: { x: number; y: number }) => p,
+  }),
+  useStore: () => ({ x: 0, y: 0, zoom: 1 }),
+}));
+
+import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js";
+
+describe("useMermaidPopupPosition · full-collision fallback", () => {
+  it("falls back to bottom-center when all candidates are occupied", () => {
+    const { result } = renderHook(() =>
+      useMermaidPopupPosition(480, 260, { width: 1200, height: 800 }),
+    );
+    expect(result.current.x).toBe((1200 - 480) / 2);
+    expect(result.current.y).toBe(16);
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.offset.test.ts b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.offset.test.ts
new file mode 100644
index 000000000..8b660f855
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/__tests__/useMermaidPopupPosition.offset.test.ts
@@ -0,0 +1,38 @@
+import { describe, it, expect, vi } from "vitest";
+import { renderHook } from "@testing-library/react";
+
+// Container is offset from viewport origin by (500, 200). The node's viewport
+// rect spans (500..1340, 200..1000) — which, after subtracting the offset,
+// covers the entire popup container (0..840, 0..800) and should collide with
+// every candidate → bottom-center fallback.
+//
+// Pre-fix (bug): the hook would compare the viewport rect directly against
+// container-local popup rects, so the node would appear to sit at (500..1340)
+// while candidates sit in (0..1200). Candidate 1 (left-shift to x=280, i.e.
+// popup spans 280..760 local) would look "free" and be selected incorrectly.
+vi.mock("@xyflow/react", () => ({
+  useReactFlow: () => ({
+    getNodes: () => [
+      { id: "n1", position: { x: 500, y: 200 }, width: 840, height: 800 },
+    ],
+    flowToScreenPosition: (p: { x: number; y: number }) => p,
+  }),
+  useStore: () => ({ x: 0, y: 0, zoom: 1 }),
+}));
+
+import { useMermaidPopupPosition } from "../useMermaidPopupPosition.js";
+
+describe("useMermaidPopupPosition · container viewport offset", () => {
+  it("translates node viewport rects into container-local space before colliding", () => {
+    const { result } = renderHook(() =>
+      useMermaidPopupPosition(480, 260, {
+        width: 1200,
+        height: 800,
+        left: 500,
+        top: 200,
+      }),
+    );
+    expect(result.current.x).toBe((1200 - 480) / 2);
+    expect(result.current.y).toBe(16);
+  });
+});
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts b/understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts
new file mode 100644
index 000000000..2340583c0
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/useMermaidPopupPosition.ts
@@ -0,0 +1,88 @@
+import { useMemo } from "react";
+import { useReactFlow, useStore, type ReactFlowState } from "@xyflow/react";
+
+export interface PopupPosition {
+  x: number;
+  y: number;
+}
+
+export interface ContainerBounds {
+  width: number;
+  height: number;
+  /** Viewport-left of the popup container. Used to translate node rects
+   *  (which `flowToScreenPosition` returns in viewport pixels) into the
+   *  same local space as `popupRect`. Defaults to 0. */
+  left?: number;
+  /** Viewport-top of the popup container. See `left`. Defaults to 0. */
+  top?: number;
+}
+
+const MARGIN = 16;
+const HORIZONTAL_OFFSET = 80;
+
+interface Rect {
+  left: number;
+  top: number;
+  right: number;
+  bottom: number;
+}
+
+function aabbIntersect(a: Rect, b: Rect): boolean {
+  return !(
+    a.right < b.left ||
+    a.left > b.right ||
+    a.bottom < b.top ||
+    a.top > b.bottom
+  );
+}
+
+export function useMermaidPopupPosition(
+  width: number,
+  height: number,
+  container: ContainerBounds,
+): PopupPosition {
+  const rf = useReactFlow();
+  const viewport = useStore((s: ReactFlowState) => ({ x: s.transform[0], y: s.transform[1], zoom: s.transform[2] }));
+
+  return useMemo(() => {
+    const centerX = (container.width - width) / 2;
+    const candidates: PopupPosition[] = [
+      { x: centerX, y: MARGIN },
+      { x: centerX - HORIZONTAL_OFFSET, y: MARGIN },
+      { x: centerX + HORIZONTAL_OFFSET, y: MARGIN },
+      { x: MARGIN, y: MARGIN },
+      { x: container.width - width - MARGIN, y: MARGIN },
+    ];
+
+    const nodes = rf.getNodes();
+    const originX = container.left ?? 0;
+    const originY = container.top ?? 0;
+
+    for (const c of candidates) {
+      const popupRect: Rect = {
+        left: c.x,
+        right: c.x + width,
+        top: container.height - c.y - height,
+        bottom: container.height - c.y,
+      };
+      const hit = nodes.some((n) => {
+        const topLeft = rf.flowToScreenPosition({
+          x: n.position.x,
+          y: n.position.y,
+        });
+        const bottomRight = rf.flowToScreenPosition({
+          x: n.position.x + (n.width ?? 180),
+          y: n.position.y + (n.height ?? 80),
+        });
+        return aabbIntersect(popupRect, {
+          left: topLeft.x - originX,
+          top: topLeft.y - originY,
+          right: bottomRight.x - originX,
+          bottom: bottomRight.y - originY,
+        });
+      });
+      if (!hit) return c;
+    }
+    return candidates[0];
+  }, [width, height, container.width, container.height, container.left, container.top, rf, viewport.x, viewport.y, viewport.zoom]);
+}
diff --git a/understand-anything-plugin/packages/dashboard/src/index.css b/understand-anything-plugin/packages/dashboard/src/index.css
index b8b6841b0..187009252 100644
--- a/understand-anything-plugin/packages/dashboard/src/index.css
+++ b/understand-anything-plugin/packages/dashboard/src/index.css
@@ -258,3 +258,14 @@ body {
 [data-theme="dark"] {
   color-scheme: dark;
 }
+
+@keyframes mermaid-popup-slide-up {
+  from {
+    transform: translateY(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
diff --git a/understand-anything-plugin/packages/dashboard/src/store.ts b/understand-anything-plugin/packages/dashboard/src/store.ts
index 73ae557e1..e07f31b74 100644
--- a/understand-anything-plugin/packages/dashboard/src/store.ts
+++ b/understand-anything-plugin/packages/dashboard/src/store.ts
@@ -75,6 +75,13 @@ interface DashboardStore {
   codeViewerOpen: boolean;
   codeViewerNodeId: string | null;
 
+  mermaidPopupNodeId: string | null;
+  mermaidModalOpen: boolean;
+  openMermaidPopup: (nodeId: string) => void;
+  closeMermaidPopup: () => void;
+  openMermaidModal: () => void;
+  closeMermaidModal: () => void;
+
   tourActive: boolean;
   currentTourStep: number;
   tourHighlightedNodeIds: string[];
@@ -349,6 +356,15 @@ export const useDashboardStore = create()((set, get) => ({
   openCodeViewer: (nodeId) => set({ codeViewerOpen: true, codeViewerNodeId: nodeId }),
   closeCodeViewer: () => set({ codeViewerOpen: false, codeViewerNodeId: null }),
 
+  mermaidPopupNodeId: null,
+  mermaidModalOpen: false,
+  openMermaidPopup: (nodeId) =>
+    set({ mermaidPopupNodeId: nodeId, mermaidModalOpen: false }),
+  closeMermaidPopup: () =>
+    set({ mermaidPopupNodeId: null, mermaidModalOpen: false }),
+  openMermaidModal: () => set({ mermaidModalOpen: true }),
+  closeMermaidModal: () => set({ mermaidModalOpen: false }),
+
   setDiffOverlay: (changed, affected) =>
     set({
       diffMode: true,
@@ -479,15 +495,16 @@ export const useDashboardStore = create()((set, get) => ({
     set({ isKnowledgeGraph: value });
   },
 
-  setViewMode: (mode) => {
-    set({
+  setViewMode: (mode) =>
+    set((state) => ({
       viewMode: mode,
       selectedNodeId: null,
       focusNodeId: null,
       codeViewerOpen: false,
       codeViewerNodeId: null,
-    });
-  },
+      mermaidPopupNodeId: mode === "domain" ? state.mermaidPopupNodeId : null,
+      mermaidModalOpen: mode === "domain" ? state.mermaidModalOpen : false,
+    })),
 
   navigateToDomain: (domainId) => {
     const { selectedNodeId, nodeHistory } = get();
diff --git a/understand-anything-plugin/packages/dashboard/src/test/setup.ts b/understand-anything-plugin/packages/dashboard/src/test/setup.ts
new file mode 100644
index 000000000..3d74ec628
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/test/setup.ts
@@ -0,0 +1,42 @@
+import "@testing-library/jest-dom/vitest";
+import { afterEach } from "vitest";
+import { cleanup } from "@testing-library/react";
+
+afterEach(() => {
+  cleanup();
+});
+
+if (!("matchMedia" in window)) {
+  Object.defineProperty(window, "matchMedia", {
+    value: (query: string) => ({
+      matches: false,
+      media: query,
+      addListener: () => {},
+      removeListener: () => {},
+      addEventListener: () => {},
+      removeEventListener: () => {},
+      dispatchEvent: () => false,
+      onchange: null,
+    }),
+  });
+}
+
+if (!("ResizeObserver" in window)) {
+  class MockResizeObserver {
+    observe() {}
+    unobserve() {}
+    disconnect() {}
+  }
+  (window as any).ResizeObserver = MockResizeObserver;
+}
+
+if (!("randomUUID" in crypto)) {
+  Object.defineProperty(crypto, "randomUUID", {
+    value: () =>
+      "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
+        const r = (Math.random() * 16) | 0;
+        const v = c === "x" ? r : (r & 0x3) | 0x8;
+        return v.toString(16);
+      }),
+  });
+}
diff --git a/understand-anything-plugin/packages/dashboard/src/test/smoke.test.tsx b/understand-anything-plugin/packages/dashboard/src/test/smoke.test.tsx
new file mode 100644
index 000000000..53bc0a9ff
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/test/smoke.test.tsx
@@ -0,0 +1,9 @@
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+
+describe("test infrastructure smoke", () => {
+  it("renders and queries the DOM", () => {
+    render(
hello world
); + expect(screen.getByText("hello world")).toBeInTheDocument(); + }); +}); diff --git a/understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts b/understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts new file mode 100644 index 000000000..9bc309282 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/themes/__tests__/mermaid-theme.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { getMermaidTheme } from "../mermaid-theme.js"; + +describe("getMermaidTheme", () => { + beforeEach(() => { + const s = document.documentElement.style; + s.setProperty("--color-elevated", "#121212"); + s.setProperty("--color-accent", "#d4a574"); + s.setProperty("--color-accent-dim", "#a88554"); + s.setProperty("--color-text-primary", "#f0e6d2"); + s.setProperty("--color-surface", "#161616"); + s.setProperty("--color-root", "#0a0a0a"); + s.setProperty("--font-sans", "'Inter', system-ui, sans-serif"); + }); + + it("reads CSS variables into Mermaid themeVariables", () => { + const theme = getMermaidTheme(); + expect(theme.background).toBe("#161616"); + expect(theme.primaryBorderColor).toBe("#d4a574"); + expect(theme.primaryColor).toBe("#a88554"); + expect(theme.fontFamily).toContain("Inter"); + }); +}); diff --git a/understand-anything-plugin/packages/dashboard/src/themes/__tests__/theme-engine.test.ts b/understand-anything-plugin/packages/dashboard/src/themes/__tests__/theme-engine.test.ts new file mode 100644 index 000000000..9e1b6598c --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/themes/__tests__/theme-engine.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect, vi } from "vitest"; +import { applyTheme, onThemeChange } from "../theme-engine.js"; + +describe("theme-engine change subscribers", () => { + it("calls registered listener after applyTheme", () => { + const listener = vi.fn(); + const unsubscribe = onThemeChange(listener); + applyTheme({ presetId: "dark-gold", accentId: "gold" }); + expect(listener).toHaveBeenCalledTimes(1); + unsubscribe(); + applyTheme({ presetId: "dark-gold", accentId: "gold" }); + expect(listener).toHaveBeenCalledTimes(1); + }); +}); diff --git a/understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts b/understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts new file mode 100644 index 000000000..2ea529901 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/themes/mermaid-theme.ts @@ -0,0 +1,45 @@ +export interface MermaidThemeVariables { + background: string; + primaryColor: string; + primaryBorderColor: string; + primaryTextColor: string; + lineColor: string; + secondaryColor: string; + tertiaryColor: string; + edgeLabelBackground: string; + mainBkg: string; + fontFamily: string; + fontSize: string; + nodeBorder: string; + clusterBkg: string; + clusterBorder: string; + defaultLinkColor: string; + titleColor: string; + labelBackground: string; + arrowheadColor: string; +} + +export function getMermaidTheme(): MermaidThemeVariables { + const style = getComputedStyle(document.documentElement); + const read = (v: string) => style.getPropertyValue(v).trim(); + return { + background: read("--color-surface"), + primaryColor: read("--color-accent-dim"), + primaryBorderColor: read("--color-accent"), + primaryTextColor: read("--color-text-primary"), + lineColor: read("--color-accent-dim"), + secondaryColor: read("--color-surface"), + tertiaryColor: read("--color-root"), + edgeLabelBackground: read("--color-elevated"), + mainBkg: read("--color-elevated"), + fontFamily: read("--font-sans"), + fontSize: "14px", + nodeBorder: read("--color-accent"), + clusterBkg: read("--color-surface"), + clusterBorder: read("--color-border-subtle"), + defaultLinkColor: read("--color-accent"), + titleColor: read("--color-text-primary"), + labelBackground: read("--color-elevated"), + arrowheadColor: read("--color-accent"), + }; +} diff --git a/understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts b/understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts index 23004ca20..78a0afafc 100644 --- a/understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts +++ b/understand-anything-plugin/packages/dashboard/src/themes/theme-engine.ts @@ -1,6 +1,14 @@ import type { ThemeConfig } from "./types.ts"; import { getAccent, getPreset } from "./presets.ts"; +type ThemeChangeListener = () => void; +const themeChangeListeners = new Set(); + +export function onThemeChange(listener: ThemeChangeListener): () => void { + themeChangeListeners.add(listener); + return () => themeChangeListeners.delete(listener); +} + export function hexToRgb(hex: string): string { const h = hex.replace("#", ""); const n = parseInt(h, 16); @@ -53,4 +61,13 @@ export function applyTheme(config: ThemeConfig): void { // 4. Set data-theme for CSS-only selectors document.documentElement.setAttribute("data-theme", preset.isDark ? "dark" : "light"); + + // 5. Notify subscribers + for (const listener of themeChangeListeners) { + try { + listener(); + } catch (err) { + console.error("[theme-engine] listener error:", err); + } + } } diff --git a/understand-anything-plugin/packages/dashboard/vitest.config.ts b/understand-anything-plugin/packages/dashboard/vitest.config.ts new file mode 100644 index 000000000..beba2e1db --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + setupFiles: ["./src/test/setup.ts"], + include: ["src/**/*.test.{ts,tsx}"], + }, +}); diff --git a/understand-anything-plugin/pnpm-lock.yaml b/understand-anything-plugin/pnpm-lock.yaml index 1e22299ee..ba5645a26 100644 --- a/understand-anything-plugin/pnpm-lock.yaml +++ b/understand-anything-plugin/pnpm-lock.yaml @@ -15,12 +15,15 @@ importers: '@types/node': specifier: ^22.0.0 version: 22.19.15 + mermaid: + specifier: ^10.9.0 + version: 10.9.5 typescript: specifier: ^5.7.0 version: 5.9.3 vitest: specifier: ^3.1.0 - version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3) packages/core: dependencies: @@ -75,13 +78,13 @@ importers: version: 25.5.0 '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4) typescript: specifier: ^5.7.0 version: 5.9.3 vitest: specifier: ^3.1.0 - version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3) packages/dashboard: dependencies: @@ -103,6 +106,12 @@ importers: hast-util-to-jsx-runtime: specifier: ^2.3.6 version: 2.3.6 + mermaid: + specifier: ^10.9.0 + version: 10.9.5 + pako: + specifier: ^2.1.0 + version: 2.1.0 react: specifier: ^19.0.0 version: 19.2.4 @@ -119,9 +128,21 @@ importers: '@tailwindcss/vite': specifier: ^4.0.0 version: 4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + '@testing-library/jest-dom': + specifier: ^6.6.0 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.1.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.5.0 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/d3-force': specifier: ^3.0.10 version: 3.0.10 + '@types/pako': + specifier: ^2.0.3 + version: 2.0.4 '@types/react': specifier: ^19.0.0 version: 19.2.14 @@ -131,6 +152,12 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.0 version: 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + '@vitest/ui': + specifier: ^3.1.0 + version: 3.2.4(vitest@3.2.4) + jsdom: + specifier: ^25.0.0 + version: 25.0.1 tailwindcss: specifier: ^4.0.0 version: 4.2.2 @@ -140,13 +167,22 @@ importers: vite: specifier: ^6.0.0 version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vitest: + specifier: ^3.1.0 + version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3) packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -218,6 +254,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -234,6 +274,37 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@dagrejs/dagre@2.0.4': resolution: {integrity: sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==} @@ -424,6 +495,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -461,66 +535,79 @@ packages: resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.0': resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.0': resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.0': resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.0': resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.0': resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.0': resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.0': resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.0': resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.0': resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.0': resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.0': resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.0': resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.0': resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} @@ -590,24 +677,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.2': resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} @@ -642,6 +733,38 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -669,9 +792,18 @@ packages: '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + '@types/d3-transition@3.0.9': resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} @@ -693,6 +825,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -705,6 +840,9 @@ packages: '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -713,6 +851,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -763,6 +904,11 @@ packages: '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/ui@3.2.4': + resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} + peerDependencies: + vitest: 3.2.4 + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -775,6 +921,10 @@ packages: '@xyflow/system@0.0.75': resolution: {integrity: sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -787,10 +937,21 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -798,6 +959,9 @@ packages: ast-v8-to-istanbul@0.3.12: resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -829,6 +993,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + caniuse-lite@1.0.30001781: resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} @@ -865,23 +1033,81 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + 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==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + 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@3.33.2: + resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} @@ -890,26 +1116,88 @@ packages: 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'} @@ -924,6 +1212,20 @@ packages: 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.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -933,6 +1235,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} @@ -940,6 +1245,13 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -951,12 +1263,32 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@5.2.2: + resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} + engines: {node: '>=0.3.1'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} electron-to-chromium@1.5.325: resolution: {integrity: sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==} + elkjs@0.9.3: + resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -967,9 +1299,29 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1001,15 +1353,28 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + fuse.js@7.1.0: resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} engines: {node: '>=10'} @@ -1018,11 +1383,23 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -1030,25 +1407,64 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + 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'} + ignore@7.0.5: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + 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-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -1069,6 +1485,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1104,6 +1523,15 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1114,6 +1542,20 @@ packages: engines: {node: '>=6'} hasBin: true + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1149,24 +1591,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1184,6 +1630,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -1196,6 +1645,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1206,6 +1659,13 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + mdast-util-from-markdown@2.0.3: resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} @@ -1227,72 +1687,153 @@ packages: mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mermaid@10.9.5: + resolution: {integrity: sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ==} + + micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + micromark-factory-whitespace@2.0.1: resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + micromark-util-chunked@2.0.1: resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + micromark-util-classify-character@2.0.1: resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + micromark-util-combine-extensions@2.0.1: resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + micromark-util-decode-numeric-character-reference@2.0.2: resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + micromark-util-decode-string@2.0.1: resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + micromark-util-normalize-identifier@2.0.1: resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + micromark-util-resolve-all@2.0.1: resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + micromark-util-subtokenize@2.1.0: resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -1305,6 +1846,14 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1324,12 +1873,24 @@ packages: node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1356,14 +1917,25 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: react: ^19.2.4 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -1378,17 +1950,44 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup@4.60.0: resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + 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'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1416,6 +2015,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1448,6 +2051,10 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -1457,10 +2064,16 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.2.2: resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} @@ -1494,6 +2107,25 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tree-sitter-c-sharp@0.23.5: resolution: {integrity: sha512-xJGOeXPMmld0nES5+080N/06yY6LQi+KWGWV4LfZaZe6srJPtUtfhIbRSN7EZN6IaauzW28v6W4QHFwmeUW6HQ==} peerDependencies: @@ -1596,6 +2228,10 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1616,6 +2252,9 @@ packages: unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -1636,6 +2275,15 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -1715,9 +2363,33 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + web-tree-sitter@0.26.8: resolution: {integrity: sha512-4sUwi7ZyOrIk5KLgYLkc2A/F0LFMQnBhfb+2Cdl7ik4ePJ6JD+fk4ofI2sA5eGawBKBaK4Vntt7Ww5KcEsay4A==} + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1736,6 +2408,25 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1785,14 +2476,24 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@babel/code-frame@7.29.0': + '@asamuzakjp/css-color@3.2.0': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -1879,6 +2580,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -1904,6 +2607,28 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@braintree/sanitize-url@6.0.4': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + '@dagrejs/dagre@2.0.4': dependencies: '@dagrejs/graphlib': 3.0.4 @@ -2021,6 +2746,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.60.0': @@ -2166,6 +2893,42 @@ snapshots: tailwindcss: 4.2.2 vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.2 @@ -2204,8 +2967,16 @@ snapshots: dependencies: '@types/d3-color': 3.1.3 + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-time@3.0.4': {} + '@types/d3-transition@3.0.9': dependencies: '@types/d3-selection': 3.0.11 @@ -2231,6 +3002,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdast@3.0.15': + dependencies: + '@types/unist': 2.0.11 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -2245,6 +3020,8 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/pako@2.0.4': {} + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -2253,6 +3030,9 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -2271,7 +3051,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -2286,7 +3066,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -2326,6 +3106,17 @@ snapshots: dependencies: tinyspy: 4.0.4 + '@vitest/ui@3.2.4(vitest@3.2.4)': + dependencies: + '@vitest/utils': 3.2.4 + fflate: 0.8.2 + flatted: 3.4.2 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3) + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -2355,6 +3146,8 @@ snapshots: d3-selection: 3.0.0 d3-zoom: 3.0.0 + agent-base@7.1.4: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -2363,8 +3156,16 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + assertion-error@2.0.1: {} ast-v8-to-istanbul@0.3.12: @@ -2373,6 +3174,8 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + asynckit@0.4.0: {} + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -2399,6 +3202,11 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + caniuse-lite@1.0.30001781: {} ccount@2.0.1: {} @@ -2429,20 +3237,76 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + 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 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.2 + + cytoscape@3.33.2: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + d3-dispatch@3.0.1: {} d3-drag@3.0.0: @@ -2450,22 +3314,82 @@ snapshots: 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): @@ -2485,16 +3409,69 @@ snapshots: 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.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + dayjs@1.11.20: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 deep-eql@5.0.2: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + delayed-stream@1.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -2503,10 +3480,28 @@ snapshots: dependencies: dequal: 2.0.3 + diff@5.2.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dompurify@3.4.0: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.325: {} + elkjs@0.9.3: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2516,8 +3511,25 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.2 + entities@6.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -2563,18 +3575,50 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fflate@0.8.2: {} + + flatted@3.4.2: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + fuse.js@7.1.0: {} gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -2584,10 +3628,22 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 @@ -2612,14 +3668,42 @@ snapshots: dependencies: '@types/hast': 3.0.4 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-escaper@2.0.2: {} html-url-attributes@3.0.1: {} + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@7.0.5: {} + indent-string@4.0.0: {} + inline-style-parser@0.2.7: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -2635,6 +3719,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -2672,10 +3758,48 @@ snapshots: js-tokens@9.0.1: {} + jsdom@25.0.1: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json5@2.2.3: {} + katex@0.16.45: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + kleur@4.1.5: {} + + layout-base@1.0.2: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -2725,6 +3849,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lodash-es@4.18.1: {} + longest-streak@3.1.0: {} loupe@3.2.1: {} @@ -2735,6 +3861,8 @@ snapshots: dependencies: yallist: 3.1.1 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2749,6 +3877,25 @@ snapshots: dependencies: semver: 7.7.4 + math-intrinsics@1.1.0: {} + + mdast-util-from-markdown@1.3.1: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + decode-named-character-reference: 1.3.0 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 @@ -2834,10 +3981,58 @@ snapshots: unist-util-visit: 5.1.0 zwitch: 2.0.4 + mdast-util-to-string@3.2.0: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.4 + mermaid@10.9.5: + dependencies: + '@braintree/sanitize-url': 6.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + cytoscape: 3.33.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.20 + dompurify: 3.4.0 + elkjs: 0.9.3 + katex: 0.16.45 + khroma: 2.1.0 + lodash-es: 4.18.1 + mdast-util-from-markdown: 1.3.1 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.4.0 + ts-dedent: 2.2.0 + uuid: 9.0.1 + web-worker: 1.5.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -2857,12 +4052,25 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-destination@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-label@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + micromark-factory-label@2.0.1: dependencies: devlop: 1.1.0 @@ -2870,11 +4078,23 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-space@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 + micromark-factory-space@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-types: 2.0.2 + micromark-factory-title@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-factory-title@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -2882,6 +4102,13 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-whitespace@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-factory-whitespace@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -2889,30 +4116,61 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-util-character@1.2.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-util-chunked@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-chunked@2.0.1: dependencies: micromark-util-symbol: 2.0.1 + micromark-util-classify-character@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-util-classify-character@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-util-combine-extensions@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 + micromark-util-combine-extensions@2.0.1: dependencies: micromark-util-chunked: 2.0.1 micromark-util-types: 2.0.2 + micromark-util-decode-numeric-character-reference@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-decode-numeric-character-reference@2.0.2: dependencies: micromark-util-symbol: 2.0.1 + micromark-util-decode-string@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-decode-string@2.0.1: dependencies: decode-named-character-reference: 1.3.0 @@ -2920,24 +4178,49 @@ snapshots: micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 + micromark-util-encode@1.1.0: {} + micromark-util-encode@2.0.1: {} + micromark-util-html-tag-name@1.2.0: {} + micromark-util-html-tag-name@2.0.1: {} + micromark-util-normalize-identifier@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-normalize-identifier@2.0.1: dependencies: micromark-util-symbol: 2.0.1 + micromark-util-resolve-all@1.1.0: + dependencies: + micromark-util-types: 1.1.0 + micromark-util-resolve-all@2.0.1: dependencies: micromark-util-types: 2.0.2 + micromark-util-sanitize-uri@1.2.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-sanitize-uri@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 + micromark-util-subtokenize@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + micromark-util-subtokenize@2.1.0: dependencies: devlop: 1.1.0 @@ -2945,10 +4228,36 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-util-symbol@1.1.0: {} + micromark-util-symbol@2.0.1: {} + micromark-util-types@1.1.0: {} + micromark-util-types@2.0.2: {} + micromark@3.2.0: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + micromark@4.0.2: dependencies: '@types/debug': 4.1.13 @@ -2971,6 +4280,14 @@ snapshots: transitivePeerDependencies: - supports-color + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-indent@1.0.1: {} + minimatch@10.2.4: dependencies: brace-expansion: 5.0.5 @@ -2981,6 +4298,10 @@ snapshots: minipass@7.1.3: {} + mri@1.2.0: {} + + mrmime@2.0.1: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -2991,8 +4312,14 @@ snapshots: node-releases@2.0.36: {} + non-layered-tidy-tree-layout@2.0.2: {} + + nwsapi@2.2.23: {} + package-json-from-dist@1.0.1: {} + pako@2.1.0: {} + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -3003,6 +4330,10 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-key@3.1.1: {} path-scurry@1.11.1: @@ -3024,13 +4355,23 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + property-information@7.1.0: {} + punycode@2.3.1: {} + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 scheduler: 0.27.0 + react-is@17.0.2: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): dependencies: '@types/hast': 3.0.4 @@ -3053,6 +4394,11 @@ snapshots: react@19.2.4: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -3070,6 +4416,8 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + robust-predicates@3.0.3: {} + rollup@4.60.0: dependencies: '@types/estree': 1.0.8 @@ -3101,6 +4449,22 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.0 fsevents: 2.3.3 + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.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 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -3117,6 +4481,12 @@ snapshots: signal-exit@4.1.0: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} @@ -3150,6 +4520,10 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -3162,10 +4536,14 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + stylis@4.4.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tailwindcss@4.2.2: {} tapable@2.3.2: {} @@ -3191,6 +4569,22 @@ snapshots: tinyspy@4.0.4: {} + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + totalist@3.0.1: {} + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tree-sitter-c-sharp@0.23.5: dependencies: node-addon-api: 8.7.0 @@ -3257,6 +4651,8 @@ snapshots: trough@2.2.0: {} + ts-dedent@2.2.0: {} + typescript@5.9.3: {} undici-types@6.21.0: {} @@ -3281,6 +4677,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-stringify-position@3.0.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 @@ -3306,6 +4706,15 @@ snapshots: dependencies: react: 19.2.4 + uuid@9.0.1: {} + + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.2 + kleur: 4.1.5 + sade: 1.8.1 + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -3388,7 +4797,7 @@ snapshots: lightningcss: 1.32.0 yaml: 2.8.3 - vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -3416,6 +4825,8 @@ snapshots: optionalDependencies: '@types/debug': 4.1.13 '@types/node': 22.19.15 + '@vitest/ui': 3.2.4(vitest@3.2.4) + jsdom: 25.0.1 transitivePeerDependencies: - jiti - less @@ -3430,7 +4841,7 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -3458,6 +4869,8 @@ snapshots: optionalDependencies: '@types/debug': 4.1.13 '@types/node': 25.5.0 + '@vitest/ui': 3.2.4(vitest@3.2.4) + jsdom: 25.0.1 transitivePeerDependencies: - jiti - less @@ -3472,8 +4885,27 @@ snapshots: - tsx - yaml + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + web-tree-sitter@0.26.8: {} + web-worker@1.5.0: {} + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3495,6 +4927,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.2.0 + ws@8.20.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} yaml@2.8.3: {} diff --git a/understand-anything-plugin/scripts/validate-mermaid.mjs b/understand-anything-plugin/scripts/validate-mermaid.mjs new file mode 100755 index 000000000..750c21942 --- /dev/null +++ b/understand-anything-plugin/scripts/validate-mermaid.mjs @@ -0,0 +1,34 @@ +#!/usr/bin/env node +// Reads JSONL on stdin: { "id": string, "source": string } per line. +// Writes JSONL on stdout: { "id": string, "ok": boolean, "error"?: string } per line. +// Exit code 0 regardless of parse failures — the caller inspects the output lines. + +import { createInterface } from "node:readline"; +import mermaid from "mermaid"; + +mermaid.initialize({ startOnLoad: false }); + +const rl = createInterface({ input: process.stdin, crlfDelay: Infinity }); + +for await (const line of rl) { + const trimmed = line.trim(); + if (!trimmed) continue; + let payload; + try { + payload = JSON.parse(trimmed); + } catch (err) { + process.stdout.write( + JSON.stringify({ id: null, ok: false, error: `invalid-json: ${err.message}` }) + "\n", + ); + continue; + } + const { id, source } = payload; + try { + await mermaid.parse(source); + process.stdout.write(JSON.stringify({ id, ok: true }) + "\n"); + } catch (err) { + process.stdout.write( + JSON.stringify({ id, ok: false, error: String(err?.message ?? err) }) + "\n", + ); + } +} diff --git a/understand-anything-plugin/vitest.config.ts b/understand-anything-plugin/vitest.config.ts new file mode 100644 index 000000000..ae847ff6d --- /dev/null +++ b/understand-anything-plugin/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/**/*.test.ts"], + }, +});