diff --git a/README.md b/README.md index 51233d1..a2628de 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ Toolbox is a self-hosted web app that brings together everything you need to pre |---------|-------------| | ๐Ÿค– **AI Learning Chat** | Session-based chat with 4 AI personas (Socratic, ELI5, Strict, Devil's Advocate), topic-specific starter prompts, and concept map generation | | ๐Ÿ“– **Knowledge Guide** | 7-pillar structured library with AI-assisted Commit flow to save learnings from chat sessions | -| ๐ŸŽจ **Architecture Builder** | Drag-and-drop whiteboard with 20+ components, bezier connections, templates, and AI design verification | +| ๐ŸŽจ **Architecture Builder** | React Flow-powered whiteboard with 25+ components, drag-to-connect anchors, minimap, multi-select, templates, and AI design verification | | ๐Ÿ“š **Flashcards + SRS** | SM-2 spaced repetition system with per-deck settings, card browser, deck stats, and a study activity heatmap | -| ๐Ÿ•ธ๏ธ **Knowledge Graph** | Interactive prerequisite map of ~60 system design concepts with a live SM-2 retention heatmap, readiness filters, curated learning tracks, and deep links into Guide, Builder, and Flashcards | +| ๐Ÿ•ธ๏ธ **Knowledge Graph** | Layered prerequisite map of ~60 system design concepts (React Flow + dagre) with a live SM-2 retention heatmap, lineage highlighting, readiness filters, curated learning tracks, and deep links into Guide, Builder, and Flashcards | | ๐Ÿงฑ **Adaptive Remediation** | Fail an advanced card and the SRS engine detects shaky foundations, then queues the prerequisite cards into your next review session | | ๐Ÿงฎ **BotE Calculator** | Back-of-the-envelope sizing sandbox: live QPS/storage/cache/bandwidth/hardware math, scenario presets, an interactive latency cheat-sheet with a budget composer, AI "Audit My Math", and 1-click Markdown export | | ๐Ÿง  **Feynman Simulator** | Voice-enabled Feynman technique: explain a concept, get structured AI feedback on gaps | @@ -123,7 +123,7 @@ All user data lives in a single SQLite file โ€” back it up by copying that file. ## ๐Ÿ› ๏ธ Tech Stack -React 19 ยท Vite ยท React Router ยท Zustand ยท Vanilla CSS ยท d3-force ยท Node.js ยท Express ยท SQLite (`better-sqlite3`) ยท Vercel AI SDK (Gemini / Claude / OpenAI / BYOM) ยท Docker +React 19 ยท Vite ยท React Router ยท Zustand ยท React Flow (`@xyflow/react`) ยท dagre ยท Vanilla CSS ยท Node.js ยท Express ยท SQLite (`better-sqlite3`) ยท Vercel AI SDK (Gemini / Claude / OpenAI / BYOM) ยท Docker --- diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 38fa975..6484888 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -17,8 +17,9 @@ | Layer | Choice | Why | |-------|--------|-----| | Frontend | React 19 + Vite | Established in project | -| Routing | React Router DOM v6 | Established in project | +| Routing | React Router DOM v7 | Established in project | | State | Zustand (persisted stores) | Established in project | +| Diagrams | React Flow (`@xyflow/react`) + dagre | Whiteboard canvas + knowledge-graph layout | | Styling | **Vanilla CSS only** | See CSS rules below | | Icons | `lucide-react` only | Consistent icon set | | Backend | Node.js + Express | Simple, matches dev experience | @@ -242,3 +243,7 @@ The Vite proxy config in `vite.config.js` forwards `/api/*` to the backend autom 5. **Mobile detection**: Don't use CSS-only responsive tricks for conditional rendering. Use the `isMobile` state pattern (window resize listener) that's established in each page component. 6. **Toast notifications**: Use `addToast({ type: 'success'|'error'|'info', message: '...' })` from `useAppStore` for user feedback. Never use `alert()`. + +7. **Two board shapes**: The app store holds whiteboard state in the React Flow shape (`position: {x,y}`, `data: {...}`, `source`/`target`). The database, the templates, and the server keep the legacy shape (`x`/`y`, `from`/`to`, anchors). Convert ONLY at the boundaries with `src/components/builder/boardModel.js` โ€” never persist the React Flow shape. + +8. **Global `svg { max-width: 100% }` reset**: React Flow's edge SVGs collapse to zero width under it. The `.react-flow svg { max-width: none }` override in `index.css` protects them โ€” keep it if you touch the reset. diff --git a/docs/architecture.md b/docs/architecture.md index 3d47a9d..1f34775 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -60,15 +60,16 @@ Toolbox is a **self-contained, single-binary-deployable** web application with n | Layer | Technology | Version | Role | |-------|-----------|---------|------| | **Frontend Framework** | React | 19 | UI rendering | -| **Build Tool** | Vite | 6 | Dev server, bundler | -| **Routing** | React Router DOM | 6 | Client-side routing | -| **State Management** | Zustand | 4 | Global state (persisted) | +| **Build Tool** | Vite | 8 | Dev server, bundler | +| **Routing** | React Router DOM | 7 | Client-side routing | +| **State Management** | Zustand | 5 | Global state (persisted) | +| **Diagrams** | React Flow (`@xyflow/react`) + dagre | 12 / 3 | Whiteboard canvas + layered knowledge-graph layout | | **Styling** | Vanilla CSS | โ€” | CSS Variables design system | | **Icons** | lucide-react | โ€” | SVG icon library | | **Markdown** | react-markdown + remark-gfm + KaTeX | โ€” | Rendered AI responses | -| **Backend** | Node.js + Express | 18+ / 5 | API server | -| **Database** | better-sqlite3 (SQLite) | โ€” | Synchronous SQLite bindings | -| **AI Client** | Google Generative AI SDK | โ€” | Gemini API integration | +| **Backend** | Node.js + Express | 22+ / 5 | API server | +| **Database** | better-sqlite3 (SQLite) | 13 | Synchronous SQLite bindings | +| **AI Client** | Vercel AI SDK | โ€” | Gemini / Claude / OpenAI / BYOM integration | | **Container** | Docker (Alpine Linux) | โ€” | Production container | | **Testing** | Vitest + React Testing Library | โ€” | Unit and integration tests | @@ -113,7 +114,8 @@ src/ โ”‚ โ”‚ โ””โ”€โ”€ PillarNav.jsx # Topic navigation within a pillar โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ builder/ # Architecture whiteboard components -โ”‚ โ”‚ โ”œโ”€โ”€ Canvas.jsx # SVG canvas with pan/zoom and node/edge rendering +โ”‚ โ”‚ โ”œโ”€โ”€ Canvas.jsx # React Flow canvas (drag, connect, minimap, controls) +โ”‚ โ”‚ โ”œโ”€โ”€ boardModel.js # Persisted board shape โ†” React Flow converters + icon/color maps โ”‚ โ”‚ โ”œโ”€โ”€ Toolbox.jsx # Component palette sidebar โ”‚ โ”‚ โ”œโ”€โ”€ BoardList.jsx # Board tab bar โ”‚ โ”‚ โ””โ”€โ”€ TemplateGallery.jsx # Pre-built template picker diff --git a/docs/screenshots/screenshot-builder.png b/docs/screenshots/screenshot-builder.png index 117ba35..71f2ac2 100644 Binary files a/docs/screenshots/screenshot-builder.png and b/docs/screenshots/screenshot-builder.png differ diff --git a/docs/screenshots/screenshot-graph.png b/docs/screenshots/screenshot-graph.png index 0b58fec..98e584a 100644 Binary files a/docs/screenshots/screenshot-graph.png and b/docs/screenshots/screenshot-graph.png differ diff --git a/docs/user-guide.md b/docs/user-guide.md index 543336a..fc6389d 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -126,33 +126,32 @@ While viewing a topic, click **Ask AI** to open a contextual chat panel on the r ## Builder โ€” Architecture Whiteboard -The Builder (`/builder`) is an interactive canvas for practicing architecture diagrams. +The Builder (`/builder`) is an interactive canvas for practicing architecture diagrams, powered by **React Flow**. ### Canvas - **Drag and drop** components from the left toolbox onto the canvas -- **Connect** components by dragging from connection dots (appear on hover) -- **Pan** by clicking and dragging the canvas background -- **Zoom** with `โŒ˜+`/`โŒ˜-` or the zoom controls in the toolbar +- **Connect** components by dragging between the anchor dots on any side of a card โ€” any side connects to any side +- **Remove** an edge with a single click on it, or select nodes/edges and press `Backspace`/`Delete` +- **Pan** by dragging the canvas background; **zoom** with the mouse wheel or the on-canvas controls +- **Multi-select** by dragging a selection box on empty canvas +- The **minimap** (bottom-left) shows the whole board with category colors โ€” click or drag it to jump around large diagrams +- **Fit view** (the frame icon in the canvas controls) re-centers everything ### Component Library The toolbox organizes components by category: - **Compute** โ€” Load Balancer, API Gateway, Microservice, Serverless Function, Background Worker, Message Queue, Event Bus, Stream Processor, Batch Processor, CDN/Edge Node -- **Storage** โ€” SQL Database, NoSQL Database, Cache (Redis), Object Storage (S3/GCS), Full-Text Search (Elasticsearch) -- **Clients** โ€” Web Client, Mobile Client -- **Observability** โ€” Monitoring & Alerting +- **Storage** โ€” SQL Database, NoSQL Database, Cache (Redis), Object Storage (S3/GCS), Full-Text Search (Elasticsearch), Vector Database +- **Clients** โ€” Web Client, Mobile Client, IoT Device, 3rd Party Service +- **Resiliency** โ€” Rate Limiter, Circuit Breaker, Retry/Backoff Handler, Load Shedder +- **Observability** โ€” Log Aggregator, Metrics Collector, Distributed Tracer ### Toolbar Features | Tool | Function | |------|----------| -| **Select** | Click and drag nodes, resize canvas | -| **Text** | Add free-form text labels | -| **Arrow** | Draw directional arrows | -| **Color** | Change component accent colors | -| **Undo / Redo** | Step through changes | -| **Zoom In / Out** | Adjust canvas zoom | +| **Estimate** | Open the quick BotE Calculator modal | | **Templates** | Load pre-built architecture templates | | **Export** | Download board as a PNG image | | **Save** | Persist the board to the database (`โŒ˜S`) | @@ -278,7 +277,7 @@ Access it from the sidebar (`โŒ˜6`) or navigate to `/interleaved`. ## Knowledge Graph -The Graph page (`/graph`, `โŒ˜7`) turns your flashcards and guide notes into an interactive **prerequisite map** of ~60 system design concepts, grouped by the 7 pillars. An edge means "learn this first" โ€” for example *Consistent Hashing โ†’ Distributed KV Stores โ†’ Virtual Nodes*. +The Graph page (`/graph`, `โŒ˜7`) turns your flashcards and guide notes into an interactive **prerequisite map** of ~60 system design concepts, grouped by the 7 pillars. An edge means "learn this first" โ€” for example *Consistent Hashing โ†’ Distributed KV Stores โ†’ Virtual Nodes*. A layered layout (dagre) places every prerequisite in an earlier column than its dependents, so learning always flows left โ†’ right. A minimap and canvas controls handle navigation on the large map. ### Retention Heatmap diff --git a/package-lock.json b/package-lock.json index 9a6ee74..e5cf0d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,22 @@ { "name": "toolbox", - "version": "1.3.2", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "toolbox", - "version": "1.3.2", + "version": "1.4.0", "dependencies": { "@ai-sdk/anthropic": "^4.0.39", "@ai-sdk/google": "^4.0.44", "@ai-sdk/openai": "^4.0.42", "@ai-sdk/openai-compatible": "^3.0.30", + "@dagrejs/dagre": "^3.1.1", + "@xyflow/react": "^12.11.3", "ai": "^7.0.66", - "better-sqlite3": "^12.10.0", + "better-sqlite3": "^13.0.3", "cors": "^2.8.6", - "d3-force": "^3.0.0", "dotenv": "^17.4.2", "express": "^5.2.1", "idb": "^8.0.3", @@ -24,7 +25,7 @@ "mermaid": "^11.15.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-markdown": "^9.0.1", + "react-markdown": "^10.1.0", "react-router-dom": "^7.17.0", "react-syntax-highlighter": "^16.1.1", "rehype-katex": "^7.0.1", @@ -36,7 +37,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", @@ -47,7 +48,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", - "jsdom": "^29.1.1", + "jsdom": "^30.0.1", "vite": "^8.0.12", "vitest": "^4.1.8" } @@ -185,56 +186,58 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "20 || >=22" } }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -292,14 +295,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -412,13 +415,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -452,18 +455,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -471,9 +474,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -510,9 +513,9 @@ "license": "Apache-2.0" }, "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", "dev": true, "funding": [ { @@ -530,9 +533,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", "dev": true, "funding": [ { @@ -554,9 +557,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.3.tgz", - "integrity": "sha512-DOgvIPkikIOixQRlD4YF31VN6fLLUTdrzhfRbis8vm0kMTgIbEPX0Ip/YX9fOeV9iywAS4sUUbTclpan7yYP8Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.0.tgz", + "integrity": "sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==", "dev": true, "funding": [ { @@ -570,8 +573,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" }, "engines": { "node": ">=20.19.0" @@ -605,9 +608,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", - "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz", + "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==", "dev": true, "funding": [ { @@ -649,44 +652,25 @@ "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, + "node_modules/@dagrejs/dagre": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-3.1.1.tgz", + "integrity": "sha512-zroZB1dFOFiGgv4Xcrn1DckB1o4aOikPqD2NDQPV0WM//CXGcS6xiD0rNkqHmw6FEg4tabt4nxPLwgCWT+Vb2A==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@dagrejs/graphlib": "4.0.5" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "node_modules/@dagrejs/graphlib": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-4.0.5.tgz", + "integrity": "sha512-7xrBTqIts3o+PMUZX97wSc+7TUbW+/rULzGNCTP6yooNVDXbzw4Wutg/H/xOutTB/c/k0YqOAavgPh4/Zk9PFA==", + "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -741,9 +725,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -902,9 +886,9 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.1.0", @@ -963,37 +947,18 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", - "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", - "license": "MIT", - "dependencies": { - "@chevrotain/types": "~11.1.1" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz", + "integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==", "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@chevrotain/types": "~11.1.2" } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", "dev": true, "license": "MIT", "funding": { @@ -1008,9 +973,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", "cpu": [ "arm64" ], @@ -1025,9 +990,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", "cpu": [ "arm64" ], @@ -1042,9 +1007,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", "cpu": [ "x64" ], @@ -1059,9 +1024,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", "cpu": [ "x64" ], @@ -1076,9 +1041,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", "cpu": [ "arm" ], @@ -1093,9 +1058,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", "cpu": [ "arm64" ], @@ -1113,9 +1078,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", "cpu": [ "arm64" ], @@ -1133,9 +1098,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", "cpu": [ "ppc64" ], @@ -1153,9 +1118,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", "cpu": [ "s390x" ], @@ -1173,9 +1138,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", "cpu": [ "x64" ], @@ -1193,9 +1158,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", "cpu": [ "x64" ], @@ -1213,9 +1178,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", "cpu": [ "arm64" ], @@ -1229,29 +1194,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", "cpu": [ "arm64" ], @@ -1266,9 +1212,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", "cpu": [ "x64" ], @@ -1317,9 +1263,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", + "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1331,9 +1277,18 @@ "redent": "^3.0.0" }, "engines": { - "node": ">=14", + "node": ">=22", "npm": ">=6", "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } } }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { @@ -1371,17 +1326,6 @@ } } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1540,9 +1484,9 @@ "license": "MIT" }, "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -1582,9 +1526,9 @@ "license": "MIT" }, "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", "license": "MIT" }, "node_modules/@types/d3-scale": { @@ -1699,9 +1643,9 @@ "license": "MIT" }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -1742,19 +1686,19 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -1774,9 +1718,9 @@ "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "license": "ISC" }, "node_modules/@upsetjs/venn.js": { @@ -1799,13 +1743,13 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -1825,16 +1769,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1843,13 +1787,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1870,9 +1814,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1883,13 +1827,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -1897,14 +1841,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1913,9 +1857,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -1923,13 +1867,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.8.tgz", - "integrity": "sha512-RUS2ZU2TsduVrI+9c12uTNaKrNUTsm6yFt3fueEUB9iKvyC2UP83F+sqIz00HQIah4UOL1TMoDAki8K0NjGvsA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.10.tgz", + "integrity": "sha512-EOUqfXHTXtpSHsyLHH40ts3Ue+hRhSGwzwzMlK0dTEOLSDYyOXLyr5JDGmHQWhN2DYI30gw6dVx3cdgM9FZl+Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.10", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -1941,17 +1885,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.8" + "vitest": "4.1.10" } }, "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1965,6 +1909,76 @@ "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==", "license": "Apache-2.0" }, + "node_modules/@xyflow/react": { + "version": "12.11.3", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.3.tgz", + "integrity": "sha512-G3jogHz2GWUtIOkhavUGno2YzY9u6fILIJBttfsBendb0/HWB90JG+sOTAvlIMEwyvq9zgy9V9ZQSwyQjR5QzQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.80", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", + "react": ">=17", + "react-dom": ">=17" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.80", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.80.tgz", + "integrity": "sha512-ywc3ZqG91brzWrH1WlwMdIX4goOfrpBy6AbLdVSaof/Xx9l138ijIKRExM6EkMro2F+OImGmSiA/WKcXvKVcfA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1979,9 +1993,9 @@ } }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -2036,26 +2050,25 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -2101,30 +2114,10 @@ "node": "18 || 20 || >=22" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { - "version": "2.10.36", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.36.tgz", - "integrity": "sha512-lVq/Df7LXlO79MVaaUHztSwWiG9oXoWHlgvNS51v8Dpd4+G4/VIy6qYePTw31nAVls33nUtnfezYeLkYAak9dg==", + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2135,17 +2128,15 @@ } }, "node_modules/better-sqlite3": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", - "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", - "hasInstallScript": true, + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", + "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", "license": "MIT", "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" + "node-addon-api": "^8.0.0" }, "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + "node": ">=22" } }, "node_modules/bidi-js": { @@ -2158,41 +2149,21 @@ "require-from-string": "^2.0.2" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -2202,23 +2173,36 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -2236,11 +2220,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -2249,30 +2233,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2312,9 +2272,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -2405,11 +2365,11 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" }, "node_modules/cliui": { "version": "9.0.1", @@ -2446,15 +2406,15 @@ } }, "node_modules/concurrently": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", - "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", + "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.9.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" @@ -2500,16 +2460,12 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/cookie-signature": { @@ -2590,9 +2546,9 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", - "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.1.tgz", + "integrity": "sha512-Lr0RvH9H75y9ar8h9Toy6u4lxRSCcxUq+hHcQ26sVWo6BnaQp1gwEZOYqwuYTZhyW7npyKnNLP8oJ2p1/3OZ7g==", "license": "MIT", "engines": { "node": ">=0.10" @@ -3124,9 +3080,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.22.tgz", + "integrity": "sha512-1YRnxzt/AabP3GHxnaB9/b+ZScCKu5TeF+co+BWG+lnWVIwEcTFc1FVE0WLNmNO3sA6GGXL40i5qkHfbLzpwrg==", "license": "MIT" }, "node_modules/debug": { @@ -3166,30 +3122,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3228,6 +3160,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3255,9 +3188,9 @@ "peer": true }, "node_modules/dompurify": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", - "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3296,9 +3229,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.371", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", - "integrity": "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==", + "version": "1.5.408", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.408.tgz", + "integrity": "sha512-SLoprcYpJ/OH2v2ps0+N5biv9H4/KBT3+YmmDew64TwK5y9j2wv7pMOFY7IorVkyMtEyLSCRlXKLsNlakeAlPw==", "dev": true, "license": "ISC" }, @@ -3318,15 +3251,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -3359,9 +3283,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -3378,13 +3302,15 @@ } }, "node_modules/es-toolkit": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", - "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", + "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", "license": "MIT", "workspaces": [ "docs", - "benchmarks" + "benchmarks", + "tests/types", + "tests/browser-compat" ] }, "node_modules/escalade": { @@ -3417,16 +3343,19 @@ } }, "node_modules/eslint": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -3450,7 +3379,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -3493,9 +3422,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", - "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz", + "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3636,19 +3565,10 @@ "node": ">=18.0.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3693,18 +3613,9 @@ "engines": { "node": ">= 18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/extend": { @@ -3785,12 +3696,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -3844,9 +3749,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -3876,12 +3781,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3976,12 +3875,6 @@ "node": ">= 0.4" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3996,9 +3889,9 @@ } }, "node_modules/globals": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", - "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true, "license": "MIT", "engines": { @@ -4318,9 +4211,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4339,26 +4232,6 @@ "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", "license": "ISC" }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4405,12 +4278,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -4542,39 +4409,39 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", + "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", + "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "canvas": "^3.0.0" + "canvas": "^3.2.3" }, "peerDependenciesMeta": { "canvas": { @@ -4583,15 +4450,40 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, + "node_modules/jsdom/node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4697,9 +4589,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -4713,23 +4605,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -4748,9 +4640,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -4769,9 +4661,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -4790,9 +4682,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -4811,9 +4703,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -4832,9 +4724,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -4856,9 +4748,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -4880,9 +4772,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -4904,9 +4796,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -4928,9 +4820,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -4949,9 +4841,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -5026,9 +4918,9 @@ } }, "node_modules/lucide-react": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", - "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.31.0.tgz", + "integrity": "sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5395,12 +5287,16 @@ "license": "CC0-1.0" }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -5416,26 +5312,26 @@ } }, "node_modules/mermaid": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", - "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "version": "11.16.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.1.tgz", + "integrity": "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g==", "license": "MIT", "dependencies": { - "@braintree/sanitize-url": "^7.1.1", + "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.1.1", + "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.33.1", + "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", - "dayjs": "^1.11.19", - "dompurify": "^3.3.1", + "dayjs": "^1.11.20", + "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", - "katex": "^0.16.25", + "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", @@ -6083,18 +5979,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6106,13 +5990,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -6121,21 +6005,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -6153,9 +6022,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -6171,12 +6040,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6193,34 +6056,19 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": "^18 || ^20 || >= 21" } }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, "license": "MIT", "engines": { @@ -6249,9 +6097,9 @@ } }, "node_modules/obug": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", - "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -6334,9 +6182,9 @@ } }, "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", "license": "MIT" }, "node_modules/parse-entities": { @@ -6437,9 +6285,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -6466,9 +6314,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -6486,7 +6334,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6494,33 +6342,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6547,31 +6368,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -6604,16 +6400,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6625,12 +6411,13 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -6640,12 +6427,16 @@ } }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { @@ -6663,40 +6454,25 @@ "node": ">= 0.10" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-is": { @@ -6708,9 +6484,9 @@ "peer": true }, "node_modules/react-markdown": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz", - "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -6735,9 +6511,9 @@ } }, "node_modules/react-router": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", - "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -6757,12 +6533,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", - "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", "dependencies": { - "react-router": "7.17.0" + "react-router": "7.18.2" }, "engines": { "node": ">=20.0.0" @@ -6772,6 +6548,19 @@ "react-dom": ">=18" } }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react-syntax-highlighter": { "version": "16.1.1", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", @@ -6792,20 +6581,6 @@ "react": ">= 0.14.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -6970,13 +6745,13 @@ "license": "Unlicense" }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6986,21 +6761,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" } }, "node_modules/roughjs": { @@ -7047,26 +6821,6 @@ "tslib": "^2.1.0" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7183,9 +6937,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, "license": "MIT", "engines": { @@ -7274,51 +7028,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -7371,21 +7080,12 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -7434,6 +7134,19 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -7447,15 +7160,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -7500,34 +7204,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -7536,9 +7212,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "license": "MIT", "engines": { "node": ">=18" @@ -7562,9 +7238,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -7572,22 +7248,22 @@ } }, "node_modules/tldts": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz", - "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.2" + "tldts-core": "^7.4.10" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz", - "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", "dev": true, "license": "MIT" }, @@ -7611,9 +7287,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7682,18 +7358,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7726,9 +7390,9 @@ } }, "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { "node": ">=18" @@ -7872,9 +7536,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ { @@ -7912,16 +7576,19 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -7983,16 +7650,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "bin": { @@ -8009,7 +7676,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -8061,19 +7728,19 @@ } }, "node_modules/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -8101,12 +7768,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -8269,6 +7936,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8373,9 +8053,9 @@ } }, "node_modules/zustand": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", - "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index 9a1765a..66a3adc 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,11 @@ "@ai-sdk/google": "^4.0.44", "@ai-sdk/openai": "^4.0.42", "@ai-sdk/openai-compatible": "^3.0.30", + "@dagrejs/dagre": "^3.1.1", + "@xyflow/react": "^12.11.3", "ai": "^7.0.66", - "better-sqlite3": "^12.10.0", + "better-sqlite3": "^13.0.3", "cors": "^2.8.6", - "d3-force": "^3.0.0", "dotenv": "^17.4.2", "express": "^5.2.1", "idb": "^8.0.3", @@ -31,7 +32,7 @@ "mermaid": "^11.15.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-markdown": "^9.0.1", + "react-markdown": "^10.1.0", "react-router-dom": "^7.17.0", "react-syntax-highlighter": "^16.1.1", "rehype-katex": "^7.0.1", @@ -43,7 +44,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", @@ -54,7 +55,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", - "jsdom": "^29.1.1", + "jsdom": "^30.0.1", "vite": "^8.0.12", "vitest": "^4.1.8" } diff --git a/src/__tests__/builder_flashcards.test.jsx b/src/__tests__/builder_flashcards.test.jsx index 2d38856..be13ab4 100644 --- a/src/__tests__/builder_flashcards.test.jsx +++ b/src/__tests__/builder_flashcards.test.jsx @@ -154,35 +154,29 @@ describe('Builder and Flashcards Comprehensive Test Suite', () => { expect(canvas).toBeInTheDocument() }) - it('2. selects active tools', () => { + it('2. toolbar renders the board actions', () => { render( ) - const selectBtn = screen.getByTitle('Select') - expect(selectBtn).toHaveClass('active') - - const textBtn = screen.getByTitle('Add Text') - fireEvent.click(textBtn) - - expect(textBtn).toHaveClass('active') - expect(selectBtn).not.toHaveClass('active') - - const arrowBtn = screen.getByTitle('Draw Arrow') - fireEvent.click(arrowBtn) - expect(arrowBtn).toHaveClass('active') - expect(textBtn).not.toHaveClass('active') + expect(screen.getByText('Estimate')).toBeInTheDocument() + expect(screen.getByText('Templates')).toBeInTheDocument() + expect(screen.getByText('Export')).toBeInTheDocument() + expect(screen.getByText('Save')).toBeInTheDocument() + expect(screen.getByText('Verify Design')).toBeInTheDocument() }) - it('3. canvas toolbar dividers render', () => { + it('3. React Flow chrome renders (controls + minimap + dot grid)', () => { render( ) - const dividers = document.querySelectorAll('.builder-toolbar-group .divider') - expect(dividers.length).toBe(2) + expect(document.querySelector('.react-flow')).toBeInTheDocument() + expect(document.querySelector('.react-flow__controls')).toBeInTheDocument() + expect(document.querySelector('.react-flow__minimap')).toBeInTheDocument() + expect(document.querySelector('.react-flow__background')).toBeInTheDocument() }) it('4. drag-and-drop node placement mock', () => { @@ -267,7 +261,7 @@ describe('Builder and Flashcards Comprehensive Test Suite', () => { expect(screen.getAllByText('Ask about your architecture')[0]).toBeInTheDocument() }) - it('7. node drag update mock', () => { + it('7. dropped node lands in the store with a numeric position', () => { render( @@ -275,54 +269,36 @@ describe('Builder and Flashcards Comprehensive Test Suite', () => { ) const canvas = document.querySelector('#builder-canvas') - const originalGetBoundingClientRect = canvas.getBoundingClientRect - canvas.getBoundingClientRect = vi.fn(() => ({ - left: 0, - top: 0, - width: 800, - height: 600, - })) - const itemData = { id: 'cache', name: 'Cache (Redis)', category: 'Storage', icon: 'zap' } - const dropEvent = new MouseEvent('drop', { - bubbles: true, - cancelable: true, + fireEvent.drop(canvas, { + preventDefault: vi.fn(), clientX: 170, clientY: 130, - }) - Object.defineProperty(dropEvent, 'dataTransfer', { - value: { - getData: vi.fn((format) => { - if (format === 'application/json') return JSON.stringify(itemData) - return '' - }), + dataTransfer: { + getData: vi.fn((format) => (format === 'application/json' ? JSON.stringify(itemData) : '')), }, }) - fireEvent(canvas, dropEvent) + // Rendered on the canvas const cacheElements = screen.getAllByText('Cache (Redis)') - const canvasText = cacheElements.find(el => el.closest('#builder-canvas')) - const node = canvasText.closest('div[style*="position: absolute"]') - expect(node).toBeInTheDocument() - expect(node.style.left).toBe('100px') - expect(node.style.top).toBe('100px') - - fireEvent.mouseDown(node, { - stopPropagation: vi.fn(), - clientX: 150, - clientY: 150, - }) - - fireEvent.mouseMove(canvas, { - clientX: 200, - clientY: 250, - }) - - expect(node.style.left).toBe('150px') - expect(node.style.top).toBe('200px') - - fireEvent.mouseUp(canvas) - canvas.getBoundingClientRect = originalGetBoundingClientRect + expect(cacheElements.find((el) => el.closest('#builder-canvas'))).toBeTruthy() + + // Stored in the shared state as a React Flow node + const stored = useAppStore.getState().nodes.find((n) => n.data?.name === 'Cache (Redis)') + expect(stored).toBeDefined() + expect(stored.type).toBe('component') + expect(stored.data.category).toBe('Storage') + expect(Number.isFinite(stored.position.x)).toBe(true) + expect(Number.isFinite(stored.position.y)).toBe(true) + + // Programmatic position updates flow back into the rendered node + useAppStore.setState((s) => ({ + nodes: s.nodes.map((n) => + n.id === stored.id ? { ...n, position: { x: 150, y: 200 } } : n + ), + })) + const updated = useAppStore.getState().nodes.find((n) => n.id === stored.id) + expect(updated.position).toEqual({ x: 150, y: 200 }) }) it('8. delete node from canvas', () => { diff --git a/src/__tests__/chat_integration.test.jsx b/src/__tests__/chat_integration.test.jsx index 1350eed..ec384e0 100644 --- a/src/__tests__/chat_integration.test.jsx +++ b/src/__tests__/chat_integration.test.jsx @@ -605,10 +605,8 @@ describe('Chat Integration & Comprehensive App Workflows', () => { expect(screen.getAllByText('Load Balancer').length).toBeGreaterThan(0) - // Verify active tool highlights - const drawArrowTool = screen.getByRole('button', { name: 'Draw Arrow' }) - fireEvent.click(drawArrowTool) - expect(drawArrowTool.className).toContain('active') + // The dropped component lands in the shared store (React Flow shape) + expect(useAppStore.getState().nodes.some((n) => n.data?.name === 'Load Balancer')).toBe(true) // Trigger verify design action const verifyBtn = document.querySelector('#builder-chat-btn') diff --git a/src/__tests__/learning_chat.test.jsx b/src/__tests__/learning_chat.test.jsx index d852505..bd64a5a 100644 --- a/src/__tests__/learning_chat.test.jsx +++ b/src/__tests__/learning_chat.test.jsx @@ -132,7 +132,10 @@ describe('LearningChat Component Tests', () => { await waitFor(() => { expect(chatApi.generateConceptMap).toHaveBeenCalledTimes(1) expect(screen.getByText(/Here's a concept map summarizing what we just discussed/i)).toBeInTheDocument() - expect(screen.getByText(/graph TD/)).toBeInTheDocument() + // The mermaid block either renders (wrapper div) or falls back to raw text, + // depending on how far mermaid gets in the test DOM. + const rendered = document.querySelector('.mermaid-wrapper') || screen.queryByText(/graph TD/) + expect(rendered).toBeTruthy() }) }) }) diff --git a/src/components/builder/Canvas.jsx b/src/components/builder/Canvas.jsx index 98f680f..565f157 100644 --- a/src/components/builder/Canvas.jsx +++ b/src/components/builder/Canvas.jsx @@ -1,571 +1,205 @@ -import { useState, useRef, useCallback, useEffect } from 'react' -import { Trash2, Move } from 'lucide-react' +import { useCallback, useMemo } from 'react' import { - Database, Zap, Globe, Monitor, Smartphone, Shield, - AlertTriangle, FileText, BarChart, Radio, Activity, HardDrive, - Archive, Search, Brain, Cpu, ExternalLink, Mail, Box, Cog, - Split, DoorOpen, -} from 'lucide-react' + ReactFlow, + ReactFlowProvider, + Background, + BackgroundVariant, + Controls, + MiniMap, + Handle, + Position, + ConnectionMode, + applyNodeChanges, + applyEdgeChanges, + useReactFlow, +} from '@xyflow/react' +import { Trash2, Move, Box } from 'lucide-react' import useAppStore from '../../stores/appStore' - -const iconMap = { - split: Split, 'door-open': DoorOpen, box: Box, zap: Zap, cog: Cog, - mail: Mail, radio: Radio, activity: Activity, database: Database, - 'hard-drive': HardDrive, archive: Archive, search: Search, brain: Brain, - globe: Globe, monitor: Monitor, smartphone: Smartphone, cpu: Cpu, - 'external-link': ExternalLink, shield: Shield, 'alert-triangle': AlertTriangle, - 'file-text': FileText, 'bar-chart': BarChart, -} - -const categoryColors = { - Compute: '#818cf8', - Storage: '#34d399', - Clients: '#60a5fa', - Observability: '#fbbf24', -} - -const MIN_SCALE = 0.25 -const MAX_SCALE = 3.0 -const ZOOM_STEP = 0.1 -const NODE_WIDTH = 140 +import { + BUILDER_ICONS, + categoryColor, + styleFlowEdge, +} from './boardModel' + +/** The four connection anchors, one handle per side (loose mode). */ +const HANDLES = [ + { id: 'top', position: Position.Top }, + { id: 'right', position: Position.Right }, + { id: 'bottom', position: Position.Bottom }, + { id: 'left', position: Position.Left }, +] /** - * Get the center position of a node anchor. + * One system component card on the canvas: icon chip, name, delete + * button, and a connection handle on each side. */ -function getAnchorPos(node, anchor) { - const hw = NODE_WIDTH / 2 - const hh = 30 // approximate half-height of a node - const cx = node.x + hw - const cy = node.y + hh - switch (anchor) { - case 'top': return { x: cx, y: node.y - 4 } - case 'bottom': return { x: cx, y: node.y + hh * 2 + 4 } - case 'left': return { x: node.x - 4, y: cy } - case 'right': return { x: node.x + NODE_WIDTH + 4, y: cy } - default: return { x: cx, y: cy } +function ComponentNode({ id, data, selected }) { + const Icon = BUILDER_ICONS[data.icon] || Box + const color = categoryColor(data.category) + + const removeNode = (e) => { + e.stopPropagation() + useAppStore.setState((s) => ({ + nodes: (s.nodes || []).filter((n) => n.id !== id), + edges: (s.edges || []).filter((edge) => edge.source !== id && edge.target !== id), + })) } -} -/** - * Build an SVG cubic Bรฉzier path between two anchor positions. - */ -function buildEdgePath(from, to) { - const dx = Math.abs(to.x - from.x) * 0.5 - const dy = Math.abs(to.y - from.y) * 0.5 - const offset = Math.max(dx, dy, 40) - - // Control points extend outward from the anchors - const c1x = from.x + (to.x > from.x ? offset : -offset) - const c1y = from.y - const c2x = to.x + (from.x > to.x ? offset : -offset) - const c2y = to.y - - return `M ${from.x} ${from.y} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${to.x} ${to.y}` + return ( +
+ {HANDLES.map((h) => ( + + ))} +
+
+ +
+ {data.name} + +
+
+ ) } -/** - * Interactive canvas where users can drop, position, and connect system design components. - * Supports pan, zoom, node dragging, and edge drawing. - */ -export default function Canvas({ onTransformChange }) { +const nodeTypes = { component: ComponentNode } + +function CanvasInner() { const nodes = useAppStore((s) => s.nodes || []) const edges = useAppStore((s) => s.edges || []) - const addEdge = useAppStore((s) => s.addEdge) - const removeEdge = useAppStore((s) => s.removeEdge) - const setNodes = (newNodesVal) => { - if (typeof newNodesVal === 'function') { - useAppStore.setState((state) => ({ nodes: newNodesVal(state.nodes) })) - } else { - useAppStore.setState({ nodes: newNodesVal }) - } - } - - // Dragging nodes - const [dragging, setDragging] = useState(null) - const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }) - - // Pan & Zoom - const [transform, setTransform] = useState({ x: 0, y: 0, scale: 1 }) - const [isPanning, setIsPanning] = useState(false) - const [panStart, setPanStart] = useState({ x: 0, y: 0 }) - const [spaceDown, setSpaceDown] = useState(false) - - // Edge drawing - const [connecting, setConnecting] = useState(null) // { nodeId, anchor } - const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) + const { screenToFlowPosition } = useReactFlow() - // Hover state for edges - const [hoveredEdge, setHoveredEdge] = useState(null) - - const canvasRef = useRef(null) + const onNodesChange = useCallback((changes) => { + useAppStore.setState((s) => ({ nodes: applyNodeChanges(changes, s.nodes || []) })) + }, []) - // Expose transform to parent - useEffect(() => { - if (onTransformChange) onTransformChange(transform) - }, [transform, onTransformChange]) + const onEdgesChange = useCallback((changes) => { + useAppStore.setState((s) => ({ edges: applyEdgeChanges(changes, s.edges || []) })) + }, []) - // Track space key for pan mode - useEffect(() => { - const handleKeyDown = (e) => { - if (e.code === 'Space' && !e.repeat) { - e.preventDefault() - setSpaceDown(true) - } - } - const handleKeyUp = (e) => { - if (e.code === 'Space') { - setSpaceDown(false) - setIsPanning(false) - } - } - window.addEventListener('keydown', handleKeyDown) - window.addEventListener('keyup', handleKeyUp) - return () => { - window.removeEventListener('keydown', handleKeyDown) - window.removeEventListener('keyup', handleKeyUp) - } + const onConnect = useCallback((connection) => { + useAppStore.setState((s) => { + const current = s.edges || [] + // One edge per node pair, either direction โ€” same rule as before. + const exists = current.some( + (e) => + (e.source === connection.source && e.target === connection.target) || + (e.source === connection.target && e.target === connection.source) + ) + if (exists || connection.source === connection.target) return s + const sourceNode = (s.nodes || []).find((n) => n.id === connection.source) + const edge = styleFlowEdge( + { ...connection, id: `edge-${Date.now()}` }, + sourceNode?.data?.category + ) + return { edges: [...current, edge] } + }) }, []) - // Convert screen coordinates to canvas coordinates - const screenToCanvas = useCallback((clientX, clientY) => { - const rect = canvasRef.current.getBoundingClientRect() - return { - x: (clientX - rect.left - transform.x) / transform.scale, - y: (clientY - rect.top - transform.y) / transform.scale, - } - }, [transform]) + // Clicking an edge removes it (the pre-migration behavior). + const onEdgeClick = useCallback((event, edge) => { + event.stopPropagation() + useAppStore.setState((s) => ({ edges: (s.edges || []).filter((e) => e.id !== edge.id) })) + }, []) - // Handle drops from toolbox - const handleDragOver = useCallback((e) => { + // Drops from the component toolbox (HTML5 drag & drop). + const onDragOver = useCallback((e) => { e.preventDefault() e.dataTransfer.dropEffect = 'copy' }, []) - const handleDrop = useCallback((e) => { + const onDrop = useCallback((e) => { e.preventDefault() try { const data = JSON.parse(e.dataTransfer.getData('application/json')) - const pos = screenToCanvas(e.clientX, e.clientY) - const x = pos.x - 70 - const y = pos.y - 30 - - setNodes((prev) => [ - ...prev, - { - id: `${data.id}-${Date.now()}`, - name: data.name, - icon: data.icon, - category: data.category, - x, - y, + const raw = screenToFlowPosition({ x: e.clientX, y: e.clientY }) + // Guard against a zero-size container (e.g. first paint, test DOM) + const node = { + id: `${data.id}-${Date.now()}`, + type: 'component', + position: { + x: Number.isFinite(raw.x) ? raw.x - 84 : 120, + y: Number.isFinite(raw.y) ? raw.y - 26 : 120, }, - ]) + data: { name: data.name, icon: data.icon, category: data.category }, + } + useAppStore.setState((s) => ({ nodes: [...(s.nodes || []), node] })) } catch { // Ignore invalid drops } - }, [screenToCanvas]) - - // Handle wheel zoom - const handleWheel = useCallback((e) => { - e.preventDefault() - const rect = canvasRef.current.getBoundingClientRect() - const mouseX = e.clientX - rect.left - const mouseY = e.clientY - rect.top - - setTransform((prev) => { - const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP - const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, prev.scale + delta)) - const ratio = newScale / prev.scale - - // Zoom towards cursor - const newX = mouseX - (mouseX - prev.x) * ratio - const newY = mouseY - (mouseY - prev.y) * ratio - - return { x: newX, y: newY, scale: newScale } - }) - }, []) - - // Canvas mouse down โ€” start panning - const handleCanvasMouseDown = useCallback((e) => { - // Middle-click or space+left-click starts pan - if (e.button === 1 || (spaceDown && e.button === 0)) { - e.preventDefault() - setIsPanning(true) - setPanStart({ x: e.clientX - transform.x, y: e.clientY - transform.y }) - } - }, [spaceDown, transform]) + }, [screenToFlowPosition]) - // Handle node dragging on canvas - const handleNodeMouseDown = useCallback((e, node) => { - if (spaceDown) return // Don't drag nodes while panning - e.stopPropagation() - const pos = screenToCanvas(e.clientX, e.clientY) - setDragging(node.id) - setDragOffset({ - x: pos.x - node.x, - y: pos.y - node.y, - }) - }, [spaceDown, screenToCanvas]) - - // Handle anchor click to start edge drawing - const handleAnchorMouseDown = useCallback((e, nodeId, anchor) => { - e.stopPropagation() - e.preventDefault() - setConnecting({ nodeId, anchor }) - const pos = screenToCanvas(e.clientX, e.clientY) - setMousePos(pos) - }, [screenToCanvas]) - - const handleMouseMove = useCallback( - (e) => { - // Panning - if (isPanning) { - setTransform((prev) => ({ - ...prev, - x: e.clientX - panStart.x, - y: e.clientY - panStart.y, - })) - return - } - - // Edge drawing preview - if (connecting) { - const pos = screenToCanvas(e.clientX, e.clientY) - setMousePos(pos) - return - } - - // Node dragging - if (!dragging) return - const pos = screenToCanvas(e.clientX, e.clientY) - const x = pos.x - dragOffset.x - const y = pos.y - dragOffset.y - setNodes((prev) => - prev.map((n) => (n.id === dragging ? { ...n, x, y } : n)) - ) - }, - [dragging, dragOffset, isPanning, panStart, connecting, screenToCanvas] + const minimapNodeColor = useMemo( + () => (node) => categoryColor(node.data?.category), + [] ) - // Handle anchor mouseup to complete edge - const handleAnchorMouseUp = useCallback((e, nodeId, anchor) => { - e.stopPropagation() - if (connecting && connecting.nodeId !== nodeId) { - // Prevent duplicate edges - const exists = edges.some( - (ed) => (ed.from === connecting.nodeId && ed.to === nodeId) || - (ed.from === nodeId && ed.to === connecting.nodeId) - ) - if (!exists) { - addEdge({ - id: `edge-${Date.now()}`, - from: connecting.nodeId, - fromAnchor: connecting.anchor, - to: nodeId, - toAnchor: anchor, - }) - } - } - setConnecting(null) - }, [connecting, edges, addEdge]) - - const handleMouseUp = useCallback(() => { - setDragging(null) - setIsPanning(false) - setConnecting(null) - }, []) - - const removeNode = (id) => { - setNodes((prev) => prev.filter((n) => n.id !== id)) - // Also remove edges connected to this node - const connectedEdges = edges.filter((e) => e.from === id || e.to === id) - connectedEdges.forEach((e) => removeEdge(e.id)) - } - - // Zoom in/out functions for toolbar buttons - const zoomIn = useCallback(() => { - setTransform((prev) => { - const newScale = Math.min(MAX_SCALE, prev.scale + ZOOM_STEP) - return { ...prev, scale: newScale } - }) - }, []) - - const zoomOut = useCallback(() => { - setTransform((prev) => { - const newScale = Math.max(MIN_SCALE, prev.scale - ZOOM_STEP) - return { ...prev, scale: newScale } - }) - }, []) - - // Expose zoom functions via ref on the DOM element - useEffect(() => { - if (canvasRef.current) { - canvasRef.current._zoomIn = zoomIn - canvasRef.current._zoomOut = zoomOut - canvasRef.current._getScale = () => transform.scale - } - }) - return ( -
- {/* SVG Layer for edges */} - + - - {/* Rendered edges */} - {edges.map((edge) => { - const fromNode = nodes.find((n) => n.id === edge.from) - const toNode = nodes.find((n) => n.id === edge.to) - if (!fromNode || !toNode) return null - - const from = getAnchorPos(fromNode, edge.fromAnchor) - const to = getAnchorPos(toNode, edge.toAnchor) - const pathD = buildEdgePath(from, to) - const color = categoryColors[fromNode.category] || '#818cf8' - - return ( - - {/* Invisible wider path for hover target */} - setHoveredEdge(edge.id)} - onMouseLeave={() => setHoveredEdge(null)} - onClick={(e) => { - e.stopPropagation() - removeEdge(edge.id) - }} - /> - {/* Visible edge */} - - {/* Arrow head */} - - - ) - })} - - {/* Temporary edge while connecting */} - {connecting && (() => { - const fromNode = nodes.find((n) => n.id === connecting.nodeId) - if (!fromNode) return null - const from = getAnchorPos(fromNode, connecting.anchor) - const pathD = buildEdgePath(from, mousePos) - return ( - - ) - })()} - - - - {/* Transformed content layer */} -
- {nodes.length === 0 && transform.scale === 1 && ( -
-
- -
- Drag components from the toolbox to start designing -
-
+ + + + + + {nodes.length === 0 && ( +
+ +
+ Drag components from the toolbox to start designing
- )} - - {nodes.map((node) => { - const Icon = iconMap[node.icon] || Box - const color = categoryColors[node.category] || '#818cf8' - - return ( -
handleNodeMouseDown(e, node)} - > - {/* Connection anchors */} - {['top', 'right', 'bottom', 'left'].map((pos) => { - const anchorStyle = { - position: 'absolute', - width: 10, - height: 10, - borderRadius: '50%', - background: connecting ? '#818cf8' : color, - border: '2px solid var(--color-surface)', - ...(pos === 'top' && { top: -5, left: '50%', transform: 'translateX(-50%)' }), - ...(pos === 'right' && { right: -5, top: '50%', transform: 'translateY(-50%)' }), - ...(pos === 'bottom' && { bottom: -5, left: '50%', transform: 'translateX(-50%)' }), - ...(pos === 'left' && { left: -5, top: '50%', transform: 'translateY(-50%)' }), - opacity: connecting ? 0.9 : 0.6, - transition: 'opacity var(--duration-fast), transform var(--duration-fast)', - cursor: 'crosshair', - zIndex: 20, - } - return ( -
handleAnchorMouseDown(e, node.id, pos)} - onMouseUp={(e) => handleAnchorMouseUp(e, node.id, pos)} - onMouseEnter={(e) => { - e.currentTarget.style.opacity = '1' - e.currentTarget.style.transform = pos === 'top' || pos === 'bottom' - ? 'translateX(-50%) scale(1.4)' - : 'translateY(-50%) scale(1.4)' - }} - onMouseLeave={(e) => { - e.currentTarget.style.opacity = connecting ? '0.9' : '0.6' - e.currentTarget.style.transform = pos === 'top' || pos === 'bottom' - ? 'translateX(-50%)' - : 'translateY(-50%)' - }} - /> - ) - })} - -
-
- -
- - {node.name} - - -
-
- ) - })} -
- - {/* Zoom indicator */} -
- {Math.round(transform.scale * 100)}% -
+
+ )}
) } + +/** + * Architecture whiteboard canvas, powered by React Flow. + * State lives in the app store (React Flow shape); the persisted board + * format is unchanged โ€” see boardModel.js for the boundary converters. + */ +export default function Canvas() { + return ( + + + + ) +} diff --git a/src/components/builder/Toolbox.jsx b/src/components/builder/Toolbox.jsx index 2eaa569..5974ad0 100644 --- a/src/components/builder/Toolbox.jsx +++ b/src/components/builder/Toolbox.jsx @@ -1,38 +1,6 @@ -import { - Database, Zap, Globe, Monitor, Smartphone, Shield, - AlertTriangle, FileText, BarChart, Radio, Activity, HardDrive, - Archive, Search, Brain, Cpu, ExternalLink, Mail, Box, Cog, - Split, DoorOpen, -} from 'lucide-react' +import { Box } from 'lucide-react' import { BUILDER_COMPONENTS } from '../../utils/constants' - -/** - * Icon mapper for builder toolbox items. - */ -const iconMap = { - split: Split, - 'door-open': DoorOpen, - box: Box, - zap: Zap, - cog: Cog, - mail: Mail, - radio: Radio, - activity: Activity, - database: Database, - 'hard-drive': HardDrive, - archive: Archive, - search: Search, - brain: Brain, - globe: Globe, - monitor: Monitor, - smartphone: Smartphone, - cpu: Cpu, - 'external-link': ExternalLink, - shield: Shield, - 'alert-triangle': AlertTriangle, - 'file-text': FileText, - 'bar-chart': BarChart, -} +import { BUILDER_ICONS } from './boardModel' /** * Builder component toolbox โ€” categorized palette of draggable system components. @@ -62,7 +30,7 @@ export default function Toolbox() {
{group.category}
{group.items.map((item) => { - const Icon = iconMap[item.icon] || Box + const Icon = BUILDER_ICONS[item.icon] || Box return (
[group.category, group.color]) +) + +export const DEFAULT_NODE_COLOR = '#818cf8' + +export function categoryColor(category) { + return CATEGORY_COLORS[category] || DEFAULT_NODE_COLOR +} + +/** Persisted board node โ†’ React Flow node. */ +export function toFlowNodes(boardNodes = []) { + return boardNodes.map((n) => ({ + id: n.id, + type: 'component', + position: { x: n.x || 0, y: n.y || 0 }, + data: { name: n.name, icon: n.icon, category: n.category }, + })) +} + +/** React Flow node โ†’ persisted board node. */ +export function toBoardNodes(flowNodes = []) { + return flowNodes.map((n) => ({ + id: n.id, + name: n.data?.name, + icon: n.data?.icon, + category: n.data?.category, + x: Math.round(n.position?.x || 0), + y: Math.round(n.position?.y || 0), + })) +} + +/** Style one flow edge from its source node's category color. */ +export function styleFlowEdge(edge, sourceCategory) { + const color = categoryColor(sourceCategory) + return { + ...edge, + type: 'default', + style: { stroke: color, strokeWidth: 2 }, + markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color }, + } +} + +/** Persisted board edge โ†’ React Flow edge (colored by source category). */ +export function toFlowEdges(boardEdges = [], boardNodes = []) { + const categoryById = new Map(boardNodes.map((n) => [n.id, n.category])) + return boardEdges.map((e) => + styleFlowEdge( + { + id: e.id, + source: e.from, + sourceHandle: e.fromAnchor || 'bottom', + target: e.to, + targetHandle: e.toAnchor || 'top', + }, + categoryById.get(e.from) + ) + ) +} + +/** React Flow edge โ†’ persisted board edge. */ +export function toBoardEdges(flowEdges = []) { + return flowEdges.map((e) => ({ + id: e.id, + from: e.source, + fromAnchor: e.sourceHandle || 'bottom', + to: e.target, + toAnchor: e.targetHandle || 'top', + })) +} + +/** Full board payload for persistence / export / AI verification. */ +export function toBoardData(flowNodes, flowEdges) { + return { nodes: toBoardNodes(flowNodes), edges: toBoardEdges(flowEdges) } +} diff --git a/src/components/graph/GraphCanvas.jsx b/src/components/graph/GraphCanvas.jsx index cdc0a92..5173f87 100644 --- a/src/components/graph/GraphCanvas.jsx +++ b/src/components/graph/GraphCanvas.jsx @@ -1,14 +1,53 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { ZoomIn, ZoomOut, Maximize } from 'lucide-react' +import { useMemo, useState, useCallback } from 'react' +import { + ReactFlow, + Background, + BackgroundVariant, + Controls, + MiniMap, + Handle, + Position, + MarkerType, +} from '@xyflow/react' import { ancestorsOf, descendantsOf } from '../../utils/knowledgeGraph' -import { computeLayout, HEALTH_COLORS } from './graphLayout' +import { computeLayout, HEALTH_COLORS, NODE_WIDTH, NODE_HEIGHT } from './graphLayout' -const MIN_ZOOM = 0.25 -const MAX_ZOOM = 2.5 +/** Edge colors per lineage state (SVG markers need literal colors). */ +const EDGE_COLORS = { default: '#52525b', up: '#818cf8', down: '#2dd4bf' } /** - * The SVG knowledge-graph canvas: pan, zoom, hover lineage highlighting, - * and node selection. Pure view โ€” all data comes from props. + * One concept pill: health-colored dot + name, card-count badge, + * ready/locked styling, and the numbered badge of the active track. + */ +function ConceptNode({ data }) { + const color = HEALTH_COLORS[data.health] || HEALTH_COLORS.unseen + return ( +
+ + + {data.name} + {(data.counts?.total ?? 0) > 0 && ( + {data.counts.total} + )} + {typeof data.trackPos === 'number' && ( + {data.trackPos} + )} + +
+ ) +} + +const nodeTypes = { concept: ConceptNode } + +/** + * The knowledge-graph canvas, powered by React Flow with a dagre + * layered layout (prerequisites flow left โ†’ right). Pure view โ€” all + * data comes from props. * * @param {Array} nodes - Nodes with health/strength/counts from the API. * @param {Array} edges - [{ from, to }] prerequisite edges. @@ -18,12 +57,9 @@ const MAX_ZOOM = 2.5 * @param {Function} onSelect - (nodeId|null) => void */ export default function GraphCanvas({ nodes, edges, visibleIds, trackOrder, selectedId, onSelect }) { - const containerRef = useRef(null) - const [transform, setTransform] = useState({ x: 0, y: 0, k: 1 }) const [hoverId, setHoverId] = useState(null) - const dragRef = useRef(null) - const layout = useMemo(() => computeLayout(nodes, edges), [nodes, edges]) + const positions = useMemo(() => computeLayout(nodes, edges), [nodes, edges]) // Lineage of the focused node: everything it needs (ancestors) and // everything it unlocks (descendants). @@ -36,214 +72,88 @@ export default function GraphCanvas({ nodes, edges, visibleIds, trackOrder, sele } }, [focusId, edges]) - const fitToView = useCallback(() => { - const el = containerRef.current - if (!el) return - const rect = el.getBoundingClientRect() - if (rect.width === 0 || rect.height === 0) return - const k = Math.min( - MAX_ZOOM, - Math.max(MIN_ZOOM, Math.min(rect.width / layout.width, rect.height / layout.height)) + const flowNodes = useMemo(() => nodes.map((node) => ({ + id: node.id, + type: 'concept', + position: positions.get(node.id) || { x: 0, y: 0 }, + // Explicit dimensions: the graph never applies RF dimension changes, + // and the minimap + fitView need node sizes up front. + width: NODE_WIDTH, + height: NODE_HEIGHT, + draggable: false, + connectable: false, + data: { + ...node, + nodeId: node.id, + filteredOut: Boolean(visibleIds && !visibleIds.has(node.id)), + focus: node.id === focusId || node.id === selectedId, + inLineage: Boolean(lineage && (lineage.up.has(node.id) || lineage.down.has(node.id))), + trackPos: trackOrder?.get(node.id), + }, + })), [nodes, positions, visibleIds, focusId, selectedId, lineage, trackOrder]) + + const flowEdges = useMemo(() => edges.map((edge) => { + const hidden = Boolean( + visibleIds && (!visibleIds.has(edge.from) || !visibleIds.has(edge.to)) ) - setTransform({ - x: (rect.width - layout.width * k) / 2, - y: (rect.height - layout.height * k) / 2, - k, - }) - }, [layout]) - - // Fit when the layout first resolves. - useEffect(() => { fitToView() }, [fitToView]) - - const zoomBy = (factor) => { - const el = containerRef.current - if (!el) return - const rect = el.getBoundingClientRect() - zoomAt(rect.width / 2, rect.height / 2, factor) - } - - const zoomAt = (cx, cy, factor) => { - setTransform((t) => { - const k = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, t.k * factor)) - const scale = k / t.k - return { - k, - x: cx - (cx - t.x) * scale, - y: cy - (cy - t.y) * scale, - } - }) - } - - const handleWheel = (e) => { - e.preventDefault() - const rect = containerRef.current.getBoundingClientRect() - const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12 - zoomAt(e.clientX - rect.left, e.clientY - rect.top, factor) - } - - // React attaches wheel listeners passively; bind manually so - // preventDefault stops the page from scrolling while zooming. - useEffect(() => { - const el = containerRef.current - if (!el) return - const wheel = (e) => handleWheel(e) - el.addEventListener('wheel', wheel, { passive: false }) - return () => el.removeEventListener('wheel', wheel) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - const handlePointerDown = (e) => { - if (e.button !== 0) return - dragRef.current = { startX: e.clientX, startY: e.clientY, tx: transform.x, ty: transform.y, moved: false } - e.currentTarget.setPointerCapture(e.pointerId) - } - - const handlePointerMove = (e) => { - const drag = dragRef.current - if (!drag) return - const dx = e.clientX - drag.startX - const dy = e.clientY - drag.startY - if (Math.abs(dx) + Math.abs(dy) > 3) drag.moved = true - setTransform((t) => ({ ...t, x: drag.tx + dx, y: drag.ty + dy })) - } - - const handlePointerUp = (e) => { - const drag = dragRef.current - dragRef.current = null - // A clean click (no drag) on empty canvas clears the selection. - if (drag && !drag.moved && e.target.tagName === 'svg') onSelect(null) - } - - const isDimmed = (id) => { - if (visibleIds && !visibleIds.has(id)) return true - if (lineage && id !== focusId && !lineage.up.has(id) && !lineage.down.has(id)) { - // Keep filter-visible nodes readable, lineage just glows brighter. - return false + let state = 'default' + if (lineage) { + const onUpPath = + (edge.to === focusId || lineage.up.has(edge.to)) && + (lineage.up.has(edge.from) || edge.from === focusId) + const onDownPath = + (edge.from === focusId || lineage.down.has(edge.from)) && + (lineage.down.has(edge.to) || edge.to === focusId) + state = onUpPath ? 'up' : onDownPath ? 'down' : 'faded' } - return false - } + const color = EDGE_COLORS[state] || EDGE_COLORS.default + return { + id: `${edge.from}->${edge.to}`, + source: edge.from, + target: edge.to, + className: `graph-edge ${state}`, + hidden, + markerEnd: { type: MarkerType.ArrowClosed, width: 14, height: 14, color }, + } + }), [edges, visibleIds, lineage, focusId]) - const edgeState = (edge) => { - if (visibleIds && (!visibleIds.has(edge.from) || !visibleIds.has(edge.to))) return 'hidden' - if (!lineage) return 'default' - const onUpPath = - (edge.to === focusId || lineage.up.has(edge.to)) && - (lineage.up.has(edge.from) || edge.from === focusId) - const onDownPath = - (edge.from === focusId || lineage.down.has(edge.from)) && - (lineage.down.has(edge.to) || edge.to === focusId) - if (onUpPath) return 'up' - if (onDownPath) return 'down' - return 'faded' - } + const onNodeClick = useCallback((event, node) => { + onSelect(node.id === selectedId ? null : node.id) + }, [onSelect, selectedId]) - const nodeRadius = (node) => 11 + Math.min(7, (node.counts?.total || 0) * 1.2) + const minimapNodeColor = useCallback( + (node) => HEALTH_COLORS[node.data?.health] || HEALTH_COLORS.unseen, + [] + ) return ( -
- + setHoverId(node.id)} + onNodeMouseLeave={() => setHoverId(null)} + onPaneClick={() => onSelect(null)} + nodesDraggable={false} + nodesConnectable={false} + edgesFocusable={false} + minZoom={0.15} + maxZoom={2.5} + fitView + fitViewOptions={{ padding: 0.1 }} > - - - - - - - - - - - - - - {/* Edges under nodes */} - {edges.map((edge) => { - const from = layout.positions.get(edge.from) - const to = layout.positions.get(edge.to) - if (!from || !to) return null - const state = edgeState(edge) - if (state === 'hidden') return null - const midX = (from.x + to.x) / 2 - const d = `M ${from.x} ${from.y} C ${midX} ${from.y}, ${midX} ${to.y}, ${to.x} ${to.y}` - return ( - ${edge.to}`} - d={d} - className={`graph-edge ${state}`} - markerEnd={`url(#arrow-${state === 'up' ? 'up' : state === 'down' ? 'down' : 'default'})`} - /> - ) - })} - - {/* Nodes */} - {nodes.map((node) => { - const pos = layout.positions.get(node.id) - if (!pos) return null - const dimmed = isDimmed(node.id) - const filteredOut = visibleIds && !visibleIds.has(node.id) - const r = nodeRadius(node) - const color = HEALTH_COLORS[node.health] || HEALTH_COLORS.unseen - const inLineage = lineage && (lineage.up.has(node.id) || lineage.down.has(node.id)) - const isFocus = node.id === focusId - const trackPos = trackOrder?.get(node.id) - return ( - { e.stopPropagation(); onSelect(node.id === selectedId ? null : node.id) }} - onMouseEnter={() => setHoverId(node.id)} - onMouseLeave={() => setHoverId(null)} - role="button" - aria-label={`${node.name} โ€” ${node.health}`} - id={`graph-node-${node.id}`} - > - {`${node.name}\n${node.summary}\nCards: ${node.counts?.total ?? 0}`} - {node.ready && ( - - )} - {node.id === selectedId && ( - - )} - - {typeof trackPos === 'number' && ( - - - {trackPos} - - )} - {node.name} - - ) - })} - - - - {/* Zoom controls */} -
- - - -
+ + + +
) } diff --git a/src/components/graph/graphLayout.js b/src/components/graph/graphLayout.js index 0fedab2..8affe69 100644 --- a/src/components/graph/graphLayout.js +++ b/src/components/graph/graphLayout.js @@ -1,90 +1,55 @@ /** - * @fileoverview Static force-directed layout for the knowledge graph. + * @fileoverview Layered layout for the knowledge graph. * - * The layout runs d3-force synchronously (no animation loop): - * - a strong forceX pins each node's column to its prerequisite depth, - * so learning flows left โ†’ right - * - a weak forceY pulls nodes toward their pillar's band, keeping - * related concepts vertically clustered - * - collision + charge spread nodes apart - * - * Initial positions are deterministic, so the same graph always lays - * out the same way. + * Dagre computes a left-to-right Sugiyama layout: every prerequisite + * sits in an earlier column than its dependents, and crossing + * minimization keeps the edge bundles readable. The layout is + * deterministic โ€” the same graph always lays out the same way. */ -import { - forceSimulation, - forceLink, - forceManyBody, - forceX, - forceY, - forceCollide, -} from 'd3-force' -import { nodeDepths } from '../../utils/knowledgeGraph' -import { PILLARS } from '../../utils/constants' +import dagre from '@dagrejs/dagre' -const COLUMN_SPACING = 180 -const X_OFFSET = 110 -const BAND_HEIGHT = 130 -const PADDING = 70 +/** Concept pill dimensions used for layout spacing (must match the CSS). */ +export const NODE_WIDTH = 180 +export const NODE_HEIGHT = 44 /** - * @param {Array} nodes - Graph nodes (need id + pillarId). + * @param {Array} nodes - Graph nodes (need id). * @param {Array} edges - [{ from, to }] - * @returns {{ positions: Map, width: number, height: number }} + * @returns {Map} Top-left positions per node id. */ export function computeLayout(nodes, edges) { - if (nodes.length === 0) { - return { positions: new Map(), width: 800, height: 600 } - } - - const depths = nodeDepths(nodes, edges) - const pillarOrder = new Map(PILLARS.map((p, i) => [p.id, i])) - const bandCenter = (pillarId) => - PADDING + ((pillarOrder.get(pillarId) ?? 3) + 0.5) * BAND_HEIGHT - - const simNodes = nodes.map((n, idx) => ({ - id: n.id, - depth: depths.get(n.id) ?? 0, - band: bandCenter(n.pillarId), - // Deterministic starting positions โ€” no randomness in the layout. - x: X_OFFSET + (depths.get(n.id) ?? 0) * COLUMN_SPACING, - y: bandCenter(n.pillarId) + ((idx % 7) - 3) * 14, - })) - const simLinks = edges.map((e) => ({ source: e.from, target: e.to })) - - const simulation = forceSimulation(simNodes) - .force('link', forceLink(simLinks).id((d) => d.id).distance(90).strength(0.12)) - .force('charge', forceManyBody().strength(-260)) - .force('x', forceX((d) => X_OFFSET + d.depth * COLUMN_SPACING).strength(0.85)) - .force('y', forceY((d) => d.band).strength(0.08)) - .force('collide', forceCollide(40)) - .stop() + const g = new dagre.graphlib.Graph() + g.setGraph({ + rankdir: 'LR', + nodesep: 18, + ranksep: 80, + marginx: 40, + marginy: 40, + }) + g.setDefaultEdgeLabel(() => ({})) - for (let i = 0; i < 300; i++) simulation.tick() - - const positions = new Map() - let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity - for (const n of simNodes) { - positions.set(n.id, { x: n.x, y: n.y }) - minX = Math.min(minX, n.x) - maxX = Math.max(maxX, n.x) - minY = Math.min(minY, n.y) - maxY = Math.max(maxY, n.y) + for (const node of nodes) { + g.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT }) } - - // Normalize so everything sits in positive space with padding. - const dx = PADDING - minX - const dy = PADDING - minY - for (const pos of positions.values()) { - pos.x += dx - pos.y += dy + for (const edge of edges) { + if (g.hasNode(edge.from) && g.hasNode(edge.to)) { + g.setEdge(edge.from, edge.to) + } } - return { - positions, - width: maxX - minX + PADDING * 2, - height: maxY - minY + PADDING * 2, + dagre.layout(g) + + const positions = new Map() + for (const node of nodes) { + const placed = g.node(node.id) + if (!placed) continue + // Dagre returns center coordinates; React Flow wants top-left. + positions.set(node.id, { + x: placed.x - NODE_WIDTH / 2, + y: placed.y - NODE_HEIGHT / 2, + }) } + return positions } /** Node fill/stroke colors per health bucket (matches the CSS legend). */ diff --git a/src/components/shared/ChatPanel.jsx b/src/components/shared/ChatPanel.jsx index 21b30e3..d02caf5 100644 --- a/src/components/shared/ChatPanel.jsx +++ b/src/components/shared/ChatPanel.jsx @@ -175,11 +175,12 @@ function ChatPanelContent({ page, title = 'Ask AI', placeholder = 'Ask a questio const contextString = (() => { if (context) return context if (page === 'builder' && canvasNodes.length > 0) { - const nodeNames = canvasNodes.map((n) => `${n.name} (${n.category})`).join(', ') + // Canvas state is React Flow-shaped: data.{name,category}, source/target + const nodeNames = canvasNodes.map((n) => `${n.data?.name} (${n.data?.category})`).join(', ') const edgeDescs = canvasEdges.map((e) => { - const fromNode = canvasNodes.find((n) => n.id === e.from) - const toNode = canvasNodes.find((n) => n.id === e.to) - return `${fromNode?.name || 'Unknown'} โ†’ ${toNode?.name || 'Unknown'}` + const fromNode = canvasNodes.find((n) => n.id === e.source) + const toNode = canvasNodes.find((n) => n.id === e.target) + return `${fromNode?.data?.name || 'Unknown'} โ†’ ${toNode?.data?.name || 'Unknown'}` }).join(', ') let desc = `The user is designing a system architecture with ${canvasNodes.length} components: ${nodeNames}.` if (canvasEdges.length > 0) { diff --git a/src/index.css b/src/index.css index 1d0982d..4b7ec75 100644 --- a/src/index.css +++ b/src/index.css @@ -1147,17 +1147,11 @@ textarea.input { .builder-canvas { flex: 1; position: relative; - background: - radial-gradient(circle, var(--color-border) 1px, transparent 1px); - background-size: 24px 24px; overflow: hidden; - cursor: grab; height: 100%; min-height: 0; -} - -.builder-canvas:active { - cursor: grabbing; + /* Dot grid comes from the React Flow component */ + background: var(--color-bg-primary); } .toolbox-section { @@ -5370,105 +5364,144 @@ textarea.input { flex: 1; position: relative; overflow: hidden; - background: - radial-gradient(circle at 1px 1px, var(--color-border) 1px, transparent 0) 0 0 / 28px 28px, - var(--color-bg-primary); - cursor: grab; + background: var(--color-bg-primary); } -.graph-canvas-wrap:active { - cursor: grabbing; +/* โ”€โ”€ Concept pill nodes (React Flow custom nodes) โ”€โ”€ */ +.graph-node { + display: flex; + align-items: center; + gap: 8px; + width: 180px; + height: 44px; + padding: 0 12px; + background: var(--color-surface); + border: 1.5px solid color-mix(in srgb, var(--health-color) 55%, transparent); + border-radius: var(--radius-full); + box-shadow: var(--shadow-sm); + cursor: pointer; + transition: border-color var(--duration-fast), box-shadow var(--duration-fast), opacity var(--duration-fast); } -.graph-svg { - width: 100%; - height: 100%; - display: block; - touch-action: none; +.graph-node:hover, +.graph-node.focus { + border-color: var(--health-color); + box-shadow: var(--shadow-md), 0 0 14px color-mix(in srgb, var(--health-color) 30%, transparent); } -.graph-edge { - fill: none; - stroke: var(--color-border-strong); - stroke-width: 1.2; - opacity: 0.55; - transition: opacity var(--duration-fast); +.graph-node.lineage { + border-color: var(--health-color); } -.graph-edge.faded { +.graph-node.filtered-out { opacity: 0.12; + pointer-events: none; } -.graph-edge.up { - stroke: #818cf8; - stroke-width: 2; - opacity: 0.9; +.graph-node.ready { + outline: 1px dashed var(--color-accent); + outline-offset: 3px; } -.graph-edge.down { - stroke: #2dd4bf; - stroke-width: 2; - opacity: 0.9; +.graph-node.locked { + border-style: dashed; } -.graph-node { - cursor: pointer; +.graph-node-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--health-color); + flex-shrink: 0; + box-shadow: 0 0 6px color-mix(in srgb, var(--health-color) 60%, transparent); } -.graph-node.filtered-out { - opacity: 0.12; - pointer-events: none; +.graph-node-name { + flex: 1; + min-width: 0; + font-size: var(--text-xs); + font-weight: 600; + color: var(--color-text-secondary); + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } -.graph-node-label { - font-size: 10px; - fill: var(--color-text-secondary); - text-anchor: middle; - pointer-events: none; - user-select: none; +.graph-node:hover .graph-node-name, +.graph-node.focus .graph-node-name, +.graph-node.lineage .graph-node-name { + color: var(--color-text-primary); } -.graph-node.focus .graph-node-label, -.graph-node:hover .graph-node-label { - fill: var(--color-text-primary); +.graph-node-count { + flex-shrink: 0; + font-size: 9px; font-weight: 700; + font-family: var(--font-mono); + color: var(--color-text-tertiary); + background: var(--color-bg-tertiary); + padding: 1px 6px; + border-radius: var(--radius-full); } -.graph-node-ready-ring { - fill: none; - stroke: var(--color-accent); - stroke-width: 1; - stroke-dasharray: 2 3; - opacity: 0.7; +.graph-node-track { + position: absolute; + top: -9px; + right: -6px; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--color-accent); + color: white; + font-size: 10px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-sm); } -.graph-node-selected-ring { - fill: none; - stroke: var(--color-accent); - stroke-width: 1.5; - opacity: 0.9; +/* Invisible connection ports โ€” edges attach here, users never interact */ +.graph-node-port { + width: 1px !important; + height: 1px !important; + min-width: 0 !important; + min-height: 0 !important; + background: transparent !important; + border: none !important; + pointer-events: none !important; } -.graph-node-track-badge circle { - fill: var(--color-accent); +/* Selected node ring (React Flow selection state) */ +.react-flow__node.selected .graph-node { + outline: 2px solid var(--color-accent); + outline-offset: 3px; } -.graph-node-track-badge text { - font-size: 9px; - font-weight: 700; - fill: white; - text-anchor: middle; - pointer-events: none; +/* โ”€โ”€ Prerequisite edges โ”€โ”€ + Literal stroke color so the line matches its SVG arrow marker. */ +.react-flow__edge.graph-edge .react-flow__edge-path { + stroke: #52525b; + stroke-width: 1.4; + opacity: 0.8; } -.graph-zoom-controls { - position: absolute; - bottom: var(--space-4); - right: var(--space-4); - display: flex; - flex-direction: column; - gap: var(--space-1); - z-index: 10; +.react-flow__edge.graph-edge.faded .react-flow__edge-path { + opacity: 0.12; +} + +.react-flow__edge.graph-edge.up .react-flow__edge-path { + stroke: #818cf8; + stroke-width: 2.2; + opacity: 0.95; +} + +.react-flow__edge.graph-edge.down .react-flow__edge-path { + stroke: #2dd4bf; + stroke-width: 2.2; + opacity: 0.95; } /* Node slide-over panel */ @@ -5797,3 +5830,221 @@ textarea.input { display: none; } } + +/* ============================================ + REACT FLOW โ€” design-system theme overrides + (Builder whiteboard + Knowledge Graph) + ============================================ */ + +/* Builder component nodes */ +.builder-node { + position: relative; + width: 168px; + background: var(--color-surface); + border: 1px solid var(--color-surface-border); + border-radius: var(--radius-lg); + padding: var(--space-3); + box-shadow: var(--shadow-sm); + transition: border-color var(--duration-fast), box-shadow var(--duration-fast); +} + +.builder-node:hover { + border-color: color-mix(in srgb, var(--node-color) 60%, transparent); +} + +.builder-node.selected, +.react-flow__node.selected .builder-node { + border-color: var(--node-color); + box-shadow: var(--shadow-lg), 0 0 12px color-mix(in srgb, var(--node-color) 20%, transparent); +} + +.builder-node-body { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.builder-node-icon { + width: 28px; + height: 28px; + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--node-color) 10%, transparent); + color: var(--node-color); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.builder-node-name { + font-size: var(--text-xs); + font-weight: 600; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--color-text-primary); +} + +.builder-node-delete { + position: absolute; + top: -8px; + right: -8px; + opacity: 0; + cursor: pointer; + background: var(--color-bg-elevated); + border: 1px solid var(--color-border); + border-radius: 50%; + width: 20px; + height: 20px; + padding: 0; + color: var(--color-text-secondary); + transition: opacity var(--duration-fast), color var(--duration-fast); + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} + +.builder-node:hover .builder-node-delete { + opacity: 0.9; +} + +.builder-node-delete:hover { + opacity: 1 !important; + color: var(--color-error); +} + +/* Connection handles on builder nodes */ +.builder-node .builder-node-handle { + width: 10px; + height: 10px; + background: var(--node-color); + border: 2px solid var(--color-surface); + opacity: 0.55; + transition: opacity var(--duration-fast), transform var(--duration-fast); +} + +.builder-node:hover .builder-node-handle, +.react-flow__handle.connectingfrom, +.react-flow__handle.connectingto { + opacity: 1; +} + +.builder-node .builder-node-handle:hover { + transform: scale(1.4); +} + +/* Builder edges: hint deletion on hover (click removes) */ +#builder-canvas .react-flow__edge:hover .react-flow__edge-path { + stroke: var(--color-error) !important; + stroke-dasharray: 6 3; + cursor: pointer; +} + +/* Connection preview line */ +.react-flow__connection-path { + stroke: var(--color-accent); + stroke-width: 2; + stroke-dasharray: 6 3; +} + +/* Empty-canvas prompt */ +.builder-canvas-empty { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + pointer-events: none; + z-index: 5; +} + +/* Toolbar hint text */ +.builder-toolbar-hint { + font-size: var(--text-xs); + color: var(--color-text-tertiary); +} + +.builder-toolbar-hint kbd { + background: var(--color-bg-tertiary); + padding: 1px 5px; + border-radius: 3px; + border: 1px solid var(--color-border); + font-family: var(--font-mono); + font-size: 10px; +} + +/* React Flow chrome โ†’ design system */ +.react-flow { + background: transparent; +} + +.react-flow__background { + color: var(--color-border); +} + +.react-flow__controls { + box-shadow: var(--shadow-md); + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--color-border); +} + +.react-flow__controls-button { + background: var(--color-bg-elevated); + border-bottom: 1px solid var(--color-border); + color: var(--color-text-secondary); + width: 28px; + height: 28px; +} + +.react-flow__controls-button:hover { + background: var(--color-bg-hover); + color: var(--color-text-primary); +} + +.react-flow__controls-button svg { + fill: currentColor; + max-width: 12px; + max-height: 12px; +} + +.react-flow__minimap { + background: var(--color-bg-secondary); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.react-flow__minimap-mask { + fill: color-mix(in srgb, var(--color-bg-primary) 60%, transparent); +} + +.react-flow__attribution { + background: transparent; + color: var(--color-text-disabled); + font-size: 9px; +} + +.react-flow__attribution a { + color: var(--color-text-disabled); +} + +@media (max-width: 768px) { + .react-flow__minimap { + display: none; + } + + .builder-toolbar-hint { + display: none; + } +} + +/* The global `img, svg { max-width: 100% }` reset collapses React Flow's + absolutely-positioned edge/connection SVGs to zero width โ€” undo it here. */ +.react-flow svg { + max-width: none; +} diff --git a/src/main.jsx b/src/main.jsx index 2898346..da8ca7a 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,8 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' +// React Flow base styles load first so index.css overrides win +import '@xyflow/react/dist/style.css' import './index.css' import App from './App.jsx' diff --git a/src/pages/BuilderPage.jsx b/src/pages/BuilderPage.jsx index 38af72b..fad49b5 100644 --- a/src/pages/BuilderPage.jsx +++ b/src/pages/BuilderPage.jsx @@ -1,9 +1,6 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useSearchParams } from 'react-router-dom' -import { - MessageSquare, Save, Undo, Redo, ZoomIn, ZoomOut, - MousePointer, Type, ArrowRight, Palette, Layout, Download, Calculator, -} from 'lucide-react' +import { MessageSquare, Save, Layout, Download, Calculator } from 'lucide-react' import Toolbox from '../components/builder/Toolbox' import Canvas from '../components/builder/Canvas' import BoardList from '../components/builder/BoardList' @@ -12,6 +9,12 @@ import ChatPanel from '../components/shared/ChatPanel' import useAppStore from '../stores/appStore' import useIsMobile from '../hooks/useIsMobile' import { boardsApi } from '../utils/api' +import { + toFlowNodes, + toFlowEdges, + toBoardData, + categoryColor, +} from '../components/builder/boardModel' // Auto-save debounce interval (ms) const AUTO_SAVE_DELAY = 3000 @@ -28,8 +31,6 @@ export default function BuilderPage() { const [boards, setBoards] = useState([]) const [activeBoard, setActiveBoard] = useState(null) - const [activeTool, setActiveTool] = useState('select') - const [zoomLevel, setZoomLevel] = useState(100) const [showTemplates, setShowTemplates] = useState(false) const isMobile = useIsMobile() const autoSaveTimer = useRef(null) @@ -91,12 +92,14 @@ export default function BuilderPage() { return } - // Load from server + // Load from server (persisted board shape โ†’ React Flow shape) boardsApi.get(activeBoard).then((board) => { if (board && board.data) { - setNodes(board.data.nodes || []) - setEdges(board.data.edges || []) - lastSavedData.current = JSON.stringify({ nodes: board.data.nodes || [], edges: board.data.edges || [] }) + const boardNodes = board.data.nodes || [] + const boardEdges = board.data.edges || [] + setNodes(toFlowNodes(boardNodes)) + setEdges(toFlowEdges(boardEdges, boardNodes)) + lastSavedData.current = JSON.stringify({ nodes: boardNodes, edges: boardEdges }) } else { setNodes([]) setEdges([]) @@ -111,11 +114,14 @@ export default function BuilderPage() { }, [activeBoard]) // โ”€โ”€โ”€ Auto-save: debounced save when nodes or edges change โ”€โ”€โ”€ + // Compare and persist in the board shape, so selection/drag churn in + // the React Flow state never triggers a save on its own. useEffect(() => { // Skip local (unsaved) boards if (!activeBoard || unsavedIds.has(activeBoard)) return - const currentData = JSON.stringify({ nodes, edges }) + const boardData = toBoardData(nodes, edges) + const currentData = JSON.stringify(boardData) if (currentData === lastSavedData.current) return if (autoSaveTimer.current) clearTimeout(autoSaveTimer.current) @@ -124,7 +130,7 @@ export default function BuilderPage() { const currentBoard = boards.find((b) => b.id === activeBoard) if (!currentBoard) return try { - await boardsApi.update(activeBoard, { name: currentBoard.name, data: { nodes, edges } }) + await boardsApi.update(activeBoard, { name: currentBoard.name, data: boardData }) lastSavedData.current = currentData } catch { // Silent fail for auto-save @@ -151,7 +157,7 @@ export default function BuilderPage() { if (!currentBoard) return const name = currentBoard.name - const data = { nodes, edges } + const data = toBoardData(nodes, edges) const isLocal = unsavedIds.has(activeBoard) try { @@ -279,21 +285,7 @@ export default function BuilderPage() { // โ”€โ”€โ”€ Canvas Handlers โ”€โ”€โ”€ - const handleZoomIn = () => { - const canvas = document.getElementById('builder-canvas') - if (canvas?._zoomIn) canvas._zoomIn() - } - - const handleZoomOut = () => { - const canvas = document.getElementById('builder-canvas') - if (canvas?._zoomOut) canvas._zoomOut() - } - - const handleTransformChange = (t) => { - setZoomLevel(Math.round(t.scale * 100)) - } - - // Load template onto canvas + // Load template onto canvas (templates ship in the persisted board shape) const handleLoadTemplate = (template) => { const ts = Date.now() const idMap = {} @@ -308,18 +300,19 @@ export default function BuilderPage() { from: idMap[e.from] || e.from, to: idMap[e.to] || e.to, })) - setNodes(newNodes) - setEdges(newEdges) + setNodes(toFlowNodes(newNodes)) + setEdges(toFlowEdges(newEdges, newNodes)) setShowTemplates(false) addToast({ type: 'success', message: `Loaded "${template.name}" template` }) } - // Export board as PNG image + // Export board as PNG image (draws from the persisted board shape) const handleExportImage = useCallback(() => { const canvasEl = document.getElementById('builder-canvas') if (!canvasEl) return try { + const { nodes: boardNodes, edges: boardEdges } = toBoardData(nodes, edges) const rect = canvasEl.getBoundingClientRect() const canvas2d = document.createElement('canvas') const scale = 2 @@ -341,17 +334,10 @@ export default function BuilderPage() { } } - const catColors = { - Compute: '#818cf8', - Storage: '#34d399', - Clients: '#60a5fa', - Observability: '#fbbf24', - } - ctx.scale(scale, scale) - nodes.forEach((node) => { - const color = catColors[node.category] || '#818cf8' + boardNodes.forEach((node) => { + const color = categoryColor(node.category) const x = node.x || 0 const y = node.y || 0 const w = 140 @@ -376,9 +362,9 @@ export default function BuilderPage() { ctx.fillText(node.name || 'Node', x + 14, y + h / 2) }) - edges.forEach((edge) => { - const fromNode = nodes.find((n) => n.id === edge.from) - const toNode = nodes.find((n) => n.id === edge.to) + boardEdges.forEach((edge) => { + const fromNode = boardNodes.find((n) => n.id === edge.from) + const toNode = boardNodes.find((n) => n.id === edge.to) if (!fromNode || !toNode) return const fromX = (fromNode.x || 0) + 70 @@ -386,7 +372,7 @@ export default function BuilderPage() { const toX = (toNode.x || 0) + 70 const toY = toNode.y || 0 - const fromColor = catColors[fromNode.category] || '#818cf8' + const fromColor = categoryColor(fromNode.category) ctx.strokeStyle = fromColor + '88' ctx.lineWidth = 1.5 ctx.beginPath() @@ -410,13 +396,6 @@ export default function BuilderPage() { } }, [nodes, edges, boards, activeBoard, addToast]) - const tools = [ - { id: 'select', icon: MousePointer, label: 'Select' }, - { id: 'text', icon: Type, label: 'Add Text' }, - { id: 'arrow', icon: ArrowRight, label: 'Draw Arrow' }, - { id: 'color', icon: Palette, label: 'Change Color' }, - ] - return (
{/* Left: Component toolbox */} @@ -439,56 +418,10 @@ export default function BuilderPage() { {/* Toolbar */}
-
- {tools.map((tool) => { - const Icon = tool.icon - return ( - - ) - })} - -
- - - - -
- - - - {zoomLevel}% +
+ + Drag to connect anchors ยท Click an edge to remove it ยท โŒซ deletes selection -
@@ -536,7 +469,7 @@ export default function BuilderPage() { {/* Canvas */}
- + {showTemplates && ( ({ srsVersion: 0, bumpSrsVersion: () => set((s) => ({ srsVersion: s.srsVersion + 1 })), - // Canvas Whiteboard Nodes + // Whiteboard canvas state (React Flow node/edge shape; the persisted + // board format is converted at the boundary โ€” see builder/boardModel.js) nodes: [], setNodes: (nodes) => set({ nodes }), - - // Canvas Whiteboard Edges edges: [], - setEdges: (edges) => { - if (typeof edges === 'function') { - set((s) => ({ edges: edges(s.edges) })) - } else { - set({ edges }) - } - }, - addEdge: (edge) => - set((s) => ({ edges: [...s.edges, edge] })), - removeEdge: (id) => - set((s) => ({ edges: s.edges.filter((e) => e.id !== id) })), + setEdges: (edges) => set({ edges }), // Aha! Moment micro-interaction ahaMomentActive: false, triggerAhaMoment: () => { diff --git a/vitest.setup.js b/vitest.setup.js index 0a2536d..665b928 100644 --- a/vitest.setup.js +++ b/vitest.setup.js @@ -37,6 +37,29 @@ window.ResizeObserver = ResizeObserver // 3. Mock scrollIntoView (unsupported by jsdom, used in ChatPanel message scroll) Element.prototype.scrollIntoView = vi.fn() +// 3b. React Flow (@xyflow/react) jsdom requirements โ€” per the official +// testing guide: DOMMatrixReadOnly, element dimensions, and SVG getBBox. +class DOMMatrixReadOnlyMock { + constructor(transform) { + const scale = transform?.match(/scale\(([\d.]+)\)/)?.[1] + this.m22 = scale !== undefined ? +scale : 1 + } +} +globalThis.DOMMatrixReadOnly = DOMMatrixReadOnlyMock + +Object.defineProperties(globalThis.HTMLElement.prototype, { + offsetHeight: { + get() { return parseFloat(this.style.height) || 1 }, + configurable: true, + }, + offsetWidth: { + get() { return parseFloat(this.style.width) || 1 }, + configurable: true, + }, +}) + +globalThis.SVGElement.prototype.getBBox = () => ({ x: 0, y: 0, width: 0, height: 0 }) + // 4. Mock global fetch API globalThis.fetch = vi.fn()