diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..719e705 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owner for everything in the repo. +* @IndAlok diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..f527a74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: Bug report +description: Report a problem with RzWeb +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug. Please fill in as much detail as you can. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug and what you expected instead. + placeholder: When I open a binary and switch to the Graph view, ... + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal steps so we can see it too. + placeholder: | + 1. Open a binary (file type / arch if relevant) + 2. Go to '...' + 3. See error + validations: + required: true + - type: input + id: binary + attributes: + label: Binary format / architecture + description: e.g. ELF x86-64, PE arm64, Mach-O. Omit if not file-specific. + validations: + required: false + - type: input + id: environment + attributes: + label: Browser & OS + description: e.g. Chrome 140 on Windows 11, Firefox 142 on macOS 15. + validations: + required: true + - type: textarea + id: console + attributes: + label: Console output + description: Any errors from the browser devtools console (will be rendered as code). + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ba5b425 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Community chat (Telegram) + url: https://telegram.dog/rizinweb + about: Questions, ideas, and general discussion. + - name: rzwasi (WebAssembly build) + url: https://github.com/IndAlok/rzwasi/issues + about: Issues with the Rizin to WASM build, missing commands, or the session API. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..de6ac79 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,25 @@ +name: Feature request +description: Suggest an idea or improvement for RzWeb +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Problem / motivation + description: What are you trying to do that RzWeb makes hard or impossible today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: What would you like RzWeb to do? + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches, or how other tools (e.g. Cutter, Binary Ninja) handle this. + validations: + required: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..aa09ac0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +version: 2 +updates: + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 10 + groups: + + minor-and-patch: + applies-to: version-updates + update-types: + - minor + - patch + security: + applies-to: security-updates + patterns: + - '*' + labels: + - dependencies + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + labels: + - dependencies + - ci diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..c530ac2 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ + + +## Summary + + + +## Related issues + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor / cleanup +- [ ] Documentation +- [ ] CI / tooling + +## Checklist + +- [ ] `npm run lint` passes (0 warnings) +- [ ] `npm run typecheck` passes +- [ ] `npm run build` succeeds +- [ ] I manually verified the change in the running app where applicable +- [ ] No new dead code, leaked listeners/observers, or `any` types introduced + +## Screenshots / notes + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dfe7f6c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + quality: + name: Lint, typecheck & build (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ['20', '22'] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup Node ${{ matrix.node }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Build + run: npm run build + env: + VITE_RIZIN_VERSION: ci + + - name: Upload build output + if: matrix.node == '22' + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + retention-days: 7 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2b748f1 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,44 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly, Monday 06:00 UTC, catches newly published advisories. + - cron: '0 6 * * 1' + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: ['javascript-typescript'] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + language: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: '/language:${{ matrix.language }}' diff --git a/.gitignore b/.gitignore index 9eb8da9..061ab7d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ *.local *.wasm .vercel +public/rizin.js +public/coi-serviceworker.min.js diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4487b6c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,32 @@ +# Code of Conduct + +## Our pledge + +We as members, contributors, and maintainers pledge to make participation in the +RzWeb community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Being respectful of differing viewpoints and experiences +- Giving and gracefully accepting constructive feedback +- Focusing on what is best for the community and the project + +Unacceptable behavior includes: + +- Harassment, insults, or derogatory comments +- Publishing others' private information without permission +- Other conduct which could reasonably be considered inappropriate + +## Enforcement + +Instances of abusive or unacceptable behavior may be reported to the maintainers +via the [Telegram community](https://telegram.dog/rizinweb) or GitHub. All +complaints will be reviewed and investigated promptly and fairly. + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), +version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b010015 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Contributing to RzWeb + +Thanks for your interest in improving RzWeb! This guide covers everything you +need to get a change merged. + +## Quick links + +- Community chat: [Telegram](https://telegram.dog/rizinweb) +- Bugs and ideas: [open an issue](https://github.com/IndAlok/rzweb/issues/new/choose) +- WebAssembly build: [rzwasi](https://github.com/IndAlok/rzwasi) + +## Project layout + +RzWeb is the React/TypeScript frontend. The Rizin reverse-engineering core is +compiled to WebAssembly in the companion [rzwasi](https://github.com/IndAlok/rzwasi) +repo and loaded at runtime from a CDN — there is no native code in this repo. + +``` +src/ + components/ UI + per-analysis views (disassembly, graph, hex, …) + lib/rizin/ Worker, RPC protocol, session logic, project bundles + stores/ Zustand stores (file, ui, settings, session, rizin) + pages/ Home + Analysis routes +``` + +The Rizin WASM module runs in a Web Worker (`src/lib/rizin/rizin.worker.ts`); +the main thread talks to it through a typed RPC facade so the UI never blocks. + +## Prerequisites + +- **Node ≥ 20.19** (the repo pins **22.12** via `.nvmrc` / `.node-version`). + If you use `nvm`/`fnm`/`asdf`, run `nvm use` (or equivalent) to match. + +## Getting started + +```bash +git clone https://github.com/IndAlok/rzweb +cd rzweb +npm install +npm run dev # http://localhost:3000 +``` + +## Before you open a PR + +All three must pass — CI enforces them and the bar is **zero warnings**: + +```bash +npm run lint # eslint, 0 warnings +npm run typecheck # tsc --noEmit +npm run build # tsc -b && vite build +``` + +Please also: + +- **Manually verify** UI changes in the running app (`npm run dev`). +- Keep diffs focused; avoid unrelated churn. +- No dead code, no leaked listeners/observers/object URLs, no `any`. +- Match the surrounding code style (naming, comment density, idioms). + +## Commit & PR conventions + +- Write clear, imperative commit subjects (e.g. `Fix hex view scroll sync`). +- Reference issues you close (`Closes #123`). +- Fill out the PR template checklist. + +## Reporting security issues + +Please do **not** file public issues for vulnerabilities — see +[SECURITY.md](SECURITY.md). + +## License + +By contributing, you agree your contributions are licensed under the same +license as this repository (see [LICENSE](LICENSE)). diff --git a/README.md b/README.md index b14662a..449e43b 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,12 @@ RzWeb is a browser-based reverse engineering interface powered by Rizin compiled ## Highlights +- Multi-session tabs: open several binaries at once, each in its own Web Worker, and switch between them with per-tab view state preserved. - Persistent Rizin sessions through the paired `rzwasi` build, so analysis state, seeks, and follow-up commands stay live inside the same binary session. - Rizin runs in a Web Worker, keeping the UI responsive during analysis, multi-MB JSON parsing, and persistence. +- Edit the binary in the browser like: patch bytes from the Hex view or terminal write commands and save the modified file at any time. +- Scripts panel with a CodeMirror editor (syntax highlighting, command-catalog autocomplete) that runs rizin cmd scripts and JS with a synchronous `rz` API, scripts can be uploaded, saved, and downloaded. +- Multiple themes with a picker, the terminal and control-flow graph track the active theme. - Full terminal access with live command autocomplete, `Tab` completion, arrow-key selection, in-terminal find, and configurable minimum characters and max results returned. - Dedicated views for disassembly, decompilation, cross-references, control-flow graphs, hex, strings, imports, exports, sections, and binary information. - Built-in decompiler view (auto-detects the build's decompiler command, e.g. `pdg`/`pdc`) with C-style syntax highlighting and one-click copy. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..38701dd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting a vulnerability + +Please report security vulnerabilities **privately** — do not open a public +issue. + +- Use [GitHub private vulnerability reporting](https://github.com/IndAlok/rzweb/security/advisories/new), or +- Reach a maintainer via the [Telegram community](https://telegram.dog/rizinweb). + +We aim to acknowledge reports within a few days and will keep you updated on a +fix and disclosure timeline. + +## Scope + +RzWeb runs entirely in the browser: binaries are loaded into WebAssembly memory +and browser storage (IndexedDB) and are **never uploaded to a server**. +Security-relevant areas include: + +- The Rizin WASM sandbox boundary (see [rzwasi](https://github.com/IndAlok/rzwasi)). +- Cross-Origin-Isolation headers (COOP/COEP) required for the WASM runtime. +- Handling of untrusted binaries and project (`.rzdb`) files. + +## Supported versions + +This is an actively developed project, fixes are done on `main` and are deployed +continuously. Please test against the latest `main` before reporting. diff --git a/package-lock.json b/package-lock.json index 76225f3..d0b274a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,63 +9,71 @@ "version": "1.0.0", "hasInstallScript": true, "dependencies": { - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-toast": "^1.2.15", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-query": "^5.90.16", - "@tanstack/react-virtual": "^3.13.18", - "@xterm/addon-clipboard": "^0.1.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/addon-web-links": "^0.11.0", - "@xterm/addon-webgl": "^0.18.0", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/language": "^6.12.3", + "@codemirror/search": "^6.7.1", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.43.1", + "@lezer/highlight": "^1.2.3", + "@radix-ui/react-dialog": "^1.1.17", + "@radix-ui/react-dropdown-menu": "^2.1.18", + "@radix-ui/react-popover": "^1.1.17", + "@radix-ui/react-progress": "^1.1.10", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-select": "^2.3.1", + "@radix-ui/react-separator": "^1.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-tabs": "^1.1.15", + "@radix-ui/react-toast": "^1.2.17", + "@radix-ui/react-tooltip": "^1.2.10", + "@tanstack/react-query": "^5.101.0", + "@tanstack/react-virtual": "^3.14.3", + "@xterm/addon-clipboard": "^0.2.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-search": "^0.16.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^5.5.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "coi-serviceworker": "^0.1.7", - "cytoscape": "^3.33.1", - "cytoscape-dagre": "^2.5.0", + "cytoscape": "^3.34.0", + "cytoscape-dagre": "^4.0.0", "idb": "^8.0.3", "lucide-react": "^0.400.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-error-boundary": "^4.1.2", - "react-hotkeys-hook": "^4.6.2", - "react-resizable-panels": "^2.1.9", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-error-boundary": "^6.1.2", + "react-hotkeys-hook": "^5.3.2", + "react-resizable-panels": "^4.11.2", "react-router-dom": "^6.30.4", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", - "zustand": "^4.5.7" + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^9.18.0", - "@types/cytoscape": "^3.21.9", - "@types/node": "^22.10.0", - "@types/react": "^18.3.27", - "@types/react-dom": "^18.3.7", + "@types/cytoscape": "^3.31.0", + "@types/node": "^26.0.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^4.7.0", - "autoprefixer": "^10.4.23", + "autoprefixer": "^10.5.0", "eslint": "^9.18.0", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.26", - "globals": "^16.0.0", - "postcss": "^8.5.6", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.6.0", + "postcss": "^8.5.15", "tailwindcss": "^3.4.19", - "typescript": "^5.9.3", - "typescript-eslint": "^8.21.0", - "vite": "^7.3.2" + "typescript": "^6.0.3", + "typescript-eslint": "^8.62.0", + "vite": "^7.3.5" }, "engines": { - "node": ">=20.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@alloc/quick-lru": { @@ -82,13 +90,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -97,9 +105,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -107,21 +115,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -148,14 +156,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -165,14 +173,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -192,9 +200,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -202,29 +210,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -244,9 +252,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -254,9 +262,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -264,9 +272,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -274,27 +282,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -335,43 +343,34 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -379,19 +378,115 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.1.tgz", + "integrity": "sha512-+BIjw/AG3tDQ4pJgTLPYdAW25eDE66YsvM4LKyVPgGzVgZ4a9Wj1SRX8kPVKgBDdPt8oHtZ15F0qx7p0oOHdHw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -879,9 +974,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -952,27 +1047,10 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -993,13 +1071,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1051,31 +1122,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -1083,9 +1154,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@humanfs/core": { @@ -1190,6 +1261,47 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1229,24 +1341,24 @@ } }, "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", + "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.10.tgz", + "integrity": "sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", @@ -1264,15 +1376,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.10.tgz", + "integrity": "sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", @@ -1289,28 +1401,10 @@ } } }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1323,9 +1417,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1338,25 +1432,25 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.17.tgz", + "integrity": "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -1373,28 +1467,10 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1407,16 +1483,16 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.13.tgz", + "integrity": "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1434,18 +1510,18 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.18.tgz", + "integrity": "sha512-PZGV82gFk0WltDRI//SsG28ZIjlo9ANTmoNYg0jLNzXXiDsAy5PkOOYQaVD1pPxY6t7gxffb1QMD6qaUvsBZdw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -1463,9 +1539,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1478,14 +1554,14 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.10.tgz", + "integrity": "sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1503,12 +1579,12 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1521,29 +1597,29 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.18.tgz", + "integrity": "sha512-lj8Rxjtn6zJq1oSbE/uDtAwCbB9BnxgHD+8MwJMuTh6u1dPamYhW9iuELr/Z8d0D/UysFblYYHeBPwi7T4k0YQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -1560,45 +1636,27 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.17.tgz", + "integrity": "sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -1615,40 +1673,22 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.1.tgz", + "integrity": "sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@radix-ui/react-arrow": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1666,13 +1706,13 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.12.tgz", + "integrity": "sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1690,13 +1730,12 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1714,12 +1753,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.6.tgz", + "integrity": "sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", @@ -1736,70 +1775,14 @@ } } }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", - "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.10.tgz", + "integrity": "sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.3", - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", @@ -1817,20 +1800,20 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.13.tgz", + "integrity": "sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -1848,20 +1831,20 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.12.tgz", + "integrity": "sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1879,32 +1862,33 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.1.tgz", + "integrity": "sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -1921,54 +1905,13 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.10.tgz", + "integrity": "sha512-Y6K6jLQCVfCnTL2MEtGxDLffkhNfEfHsEg3Wa8JU+IWdn3EWbLXd3OuOfQRN7p/W/cUce1WyTk3QeuAoDBzN9g==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", @@ -1986,12 +1929,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2004,19 +1947,19 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.15.tgz", + "integrity": "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2034,23 +1977,23 @@ } }, "node_modules/@radix-ui/react-toast": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", - "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.17.tgz", + "integrity": "sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -2068,23 +2011,23 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.3" + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.10.tgz", + "integrity": "sha512-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-visually-hidden": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -2101,28 +2044,10 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2135,13 +2060,13 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -2154,12 +2079,12 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -2172,12 +2097,12 @@ } }, "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", + "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -2190,9 +2115,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2205,9 +2130,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", + "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2220,12 +2145,12 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", + "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.1" + "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -2238,12 +2163,12 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", + "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -2256,12 +2181,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.6.tgz", + "integrity": "sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.6" }, "peerDependencies": { "@types/react": "*", @@ -2279,9 +2204,9 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", + "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", "license": "MIT" }, "node_modules/@remix-run/router": { @@ -2651,9 +2576,9 @@ ] }, "node_modules/@tanstack/query-core": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz", - "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz", + "integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==", "license": "MIT", "funding": { "type": "github", @@ -2661,12 +2586,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", - "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz", + "integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.16" + "@tanstack/query-core": "5.101.0" }, "funding": { "type": "github", @@ -2677,12 +2602,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.18", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.18.tgz", - "integrity": "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==", + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.3.tgz", + "integrity": "sha512-k/cnHPVaOfn46hSbiY6n4Dzf4QjCGWSF40zR5QIIYUqPAjpA6TN7InfYmcMiDVQGP2iUn9xsRbAl8u1v3UmeVQ==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.18" + "@tanstack/virtual-core": "3.17.1" }, "funding": { "type": "github", @@ -2694,9 +2619,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.18", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", - "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.1.tgz", + "integrity": "sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA==", "license": "MIT", "funding": { "type": "github", @@ -2749,11 +2674,15 @@ } }, "node_modules/@types/cytoscape": { - "version": "3.21.9", - "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.21.9.tgz", - "integrity": "sha512-JyrG4tllI6jvuISPjHK9j2Xv/LTbnLekLke5otGStjFluIyA9JjgnvgZrSBsp8cEDpiTjwgZUZwpPv8TSBcoLw==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.31.0.tgz", + "integrity": "sha512-EXHOHxqQjGxLDEh5cP4te6J0bi7LbCzmZkzsR6f703igUac8UGMdEohMyU3GHAayCTZrLQOMnaE/lqB2Ekh8Ww==", + "deprecated": "This is a stub types definition. cytoscape provides its own type definitions, so you do not need this installed.", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "cytoscape": "*" + } }, "node_modules/@types/estree": { "version": "1.0.8", @@ -2770,53 +2699,50 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~8.3.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "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==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", - "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", + "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.0", - "@typescript-eslint/types": "^8.53.0", - "debug": "^4.4.3" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/type-utils": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2826,29 +2752,34 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.62.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", - "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 4" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", - "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", + "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "debug": "^4.4.3" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2857,21 +2788,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", - "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", + "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0", - "@typescript-eslint/utils": "8.53.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "@typescript-eslint/tsconfig-utils": "^8.62.0", + "@typescript-eslint/types": "^8.62.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2881,16 +2811,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", - "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", + "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2899,23 +2832,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", - "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", + "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.53.0", - "@typescript-eslint/tsconfig-utils": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2924,18 +2846,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", - "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", + "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2943,67 +2868,42 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.8.4" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", - "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", + "node_modules/@typescript-eslint/types": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", + "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", - "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", + "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0" + "@typescript-eslint/project-service": "8.62.0", + "@typescript-eslint/tsconfig-utils": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3011,38 +2911,22 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", - "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", - "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "node_modules/@typescript-eslint/utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", + "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.53.0", - "@typescript-eslint/tsconfig-utils": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3052,18 +2936,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", - "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", + "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.62.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3073,32 +2958,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -3121,52 +2993,37 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz", - "integrity": "sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0.tgz", + "integrity": "sha512-Dl31BCtBhLaUEECUbEiVcCLvLBbaeGYdT7NofB8OJkGTD3MWgBsaLjXvfGAD4tQNHhm6mbKyYkR7XD8kiZsdNg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" - }, - "peerDependencies": { - "@xterm/xterm": "^5.4.0" } }, "node_modules/@xterm/addon-fit": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", - "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" }, "node_modules/@xterm/addon-search": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.15.0.tgz", - "integrity": "sha512-ZBZKLQ+EuKE83CqCmSSz5y1tx+aNOCUaA7dm6emgOX+8J9H1FWXZyrKfzjwzV+V14TV3xToz1goIeRhXBS5qjg==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/addon-web-links": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.11.0.tgz", - "integrity": "sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz", + "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==", + "license": "MIT" + }, + "node_modules/@xterm/addon-web-links": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz", + "integrity": "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==", + "license": "MIT" }, "node_modules/@xterm/addon-webgl": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz", - "integrity": "sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz", + "integrity": "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==", + "license": "MIT" }, "node_modules/@xterm/xterm": { "version": "5.5.0", @@ -3197,6 +3054,23 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3261,9 +3135,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -3281,8 +3155,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -3305,13 +3179,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", - "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binary-extensions": { @@ -3328,13 +3205,26 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -3351,9 +3241,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "dev": true, "funding": [ { @@ -3371,11 +3261,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3405,9 +3295,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001763", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz", - "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==", + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, "funding": [ { @@ -3567,6 +3457,12 @@ "dev": true, "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3603,36 +3499,23 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", "license": "MIT", "engines": { "node": ">=0.10" } }, "node_modules/cytoscape-dagre": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.5.0.tgz", - "integrity": "sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-4.0.0.tgz", + "integrity": "sha512-wsDgi1hHpWfslQxe/Gd/xCCeg2YUDrzwulhHOSI7fMfll92FFVY2AWhSRulLiOVcyDE4zz0rRd/+1TBTw+ew5Q==", "license": "MIT", - "dependencies": { - "dagre": "^0.8.5" - }, "peerDependencies": { "cytoscape": "^3.2.22" } }, - "node_modules/dagre": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", - "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", - "license": "MIT", - "dependencies": { - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3679,9 +3562,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", "dev": true, "license": "ISC" }, @@ -3824,13 +3707,13 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", "dev": true, "license": "MIT", "peerDependencies": { - "eslint": ">=8.40" + "eslint": "^9 || ^10" } }, "node_modules/eslint-scope": { @@ -3863,27 +3746,10 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3904,13 +3770,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -4198,9 +4057,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, "license": "MIT", "engines": { @@ -4210,15 +4069,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4384,13 +4234,24 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4419,6 +4280,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4499,12 +4367,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4512,18 +4374,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4568,16 +4418,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4603,9 +4453,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.14.tgz", + "integrity": "sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==", "dev": true, "funding": [ { @@ -4629,11 +4479,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -4796,9 +4649,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -4816,7 +4669,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5000,50 +4853,46 @@ "license": "MIT" }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.7" } }, "node_modules/react-error-boundary": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", - "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.2.tgz", + "integrity": "sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, "peerDependencies": { - "react": ">=16.13.1" + "react": "^18.0.0 || ^19.0.0" } }, "node_modules/react-hotkeys-hook": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.6.2.tgz", - "integrity": "sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-5.3.2.tgz", + "integrity": "sha512-DDDy9xK6mbTQ6aPlQvIl0dA/a90T/AWml4Rm21JXFDLlRHalIg4/Rv3equUQYs5xPTWq+oEl6RD7mi/nBpU3Uw==", "license": "MIT", + "workspaces": [ + "packages/*" + ], "peerDependencies": { - "react": ">=16.8.1", - "react-dom": ">=16.8.1" + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/react-refresh": { @@ -5104,13 +4953,13 @@ } }, "node_modules/react-resizable-panels": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", - "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.11.2.tgz", + "integrity": "sha512-+kfFbDZ8mygc7g0vxOcDzCVGuwiIUOnILqPoUHo6/uP+Mmyx6HzZU+kj1aOPDlktXuobYbr6BtQekvJwHRX4Eg==", "license": "MIT", "peerDependencies": { - "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/react-router": { @@ -5292,18 +5141,15 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -5369,6 +5215,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -5550,6 +5402,19 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5577,9 +5442,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5591,152 +5456,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.0.tgz", - "integrity": "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.0", - "@typescript-eslint/parser": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0", - "@typescript-eslint/utils": "8.53.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", - "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/type-utils": "8.53.0", - "@typescript-eslint/utils": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.53.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", - "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", - "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", - "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", - "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.53.0", - "@typescript-eslint/tsconfig-utils": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", - "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", + "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/eslint-plugin": "8.62.0", + "@typescript-eslint/parser": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5744,48 +5473,16 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/typescript-eslint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/typescript-eslint/node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.8.4" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, @@ -5873,15 +5570,6 @@ } } }, - "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/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5890,9 +5578,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", "dev": true, "license": "MIT", "dependencies": { @@ -5995,6 +5683,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6042,20 +5736,18 @@ } }, "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, "engines": { - "node": ">=12.7.0" + "node": ">=12.20.0" }, "peerDependencies": { - "@types/react": ">=16.8", + "@types/react": ">=18.0.0", "immer": ">=9.0.6", - "react": ">=16.8" + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -6066,6 +5758,9 @@ }, "react": { "optional": true + }, + "use-sync-external-store": { + "optional": true } } } diff --git a/package.json b/package.json index 6e85fcd..acfefc7 100644 --- a/package.json +++ b/package.json @@ -13,60 +13,68 @@ "postinstall": "node scripts/postinstall.cjs" }, "dependencies": { - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-toast": "^1.2.15", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-query": "^5.90.16", - "@tanstack/react-virtual": "^3.13.18", - "@xterm/addon-clipboard": "^0.1.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/addon-web-links": "^0.11.0", - "@xterm/addon-webgl": "^0.18.0", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/language": "^6.12.3", + "@codemirror/search": "^6.7.1", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.43.1", + "@lezer/highlight": "^1.2.3", + "@radix-ui/react-dialog": "^1.1.17", + "@radix-ui/react-dropdown-menu": "^2.1.18", + "@radix-ui/react-popover": "^1.1.17", + "@radix-ui/react-progress": "^1.1.10", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-select": "^2.3.1", + "@radix-ui/react-separator": "^1.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-tabs": "^1.1.15", + "@radix-ui/react-toast": "^1.2.17", + "@radix-ui/react-tooltip": "^1.2.10", + "@tanstack/react-query": "^5.101.0", + "@tanstack/react-virtual": "^3.14.3", + "@xterm/addon-clipboard": "^0.2.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-search": "^0.16.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^5.5.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "coi-serviceworker": "^0.1.7", - "cytoscape": "^3.33.1", - "cytoscape-dagre": "^2.5.0", + "cytoscape": "^3.34.0", + "cytoscape-dagre": "^4.0.0", "idb": "^8.0.3", "lucide-react": "^0.400.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-error-boundary": "^4.1.2", - "react-hotkeys-hook": "^4.6.2", - "react-resizable-panels": "^2.1.9", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-error-boundary": "^6.1.2", + "react-hotkeys-hook": "^5.3.2", + "react-resizable-panels": "^4.11.2", "react-router-dom": "^6.30.4", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", - "zustand": "^4.5.7" + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^9.18.0", - "@types/cytoscape": "^3.21.9", - "@types/node": "^22.10.0", - "@types/react": "^18.3.27", - "@types/react-dom": "^18.3.7", + "@types/cytoscape": "^3.31.0", + "@types/node": "^26.0.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^4.7.0", - "autoprefixer": "^10.4.23", + "autoprefixer": "^10.5.0", "eslint": "^9.18.0", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.26", - "globals": "^16.0.0", - "postcss": "^8.5.6", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.6.0", + "postcss": "^8.5.15", "tailwindcss": "^3.4.19", - "typescript": "^5.9.3", - "typescript-eslint": "^8.21.0", - "vite": "^7.3.2" + "typescript": "^6.0.3", + "typescript-eslint": "^8.62.0", + "vite": "^7.3.5" }, "engines": { "node": "^20.19.0 || >=22.12.0" diff --git a/src/components/ErrorFallback.tsx b/src/components/ErrorFallback.tsx index 3635805..1bd35b9 100644 --- a/src/components/ErrorFallback.tsx +++ b/src/components/ErrorFallback.tsx @@ -1,12 +1,8 @@ +import type { FallbackProps } from 'react-error-boundary'; import { Button } from '@/components/ui'; -export function ErrorFallback({ - error, - resetErrorBoundary, -}: { - error: Error; - resetErrorBoundary: () => void; -}) { +export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + const message = error instanceof Error ? error.message : String(error); return (
@@ -14,7 +10,7 @@ export function ErrorFallback({ Something went wrong
-          {error.message}
+          {message}
         
- - +
+
+
+ +
+
+ System + {theme === 'system' && } +
+ + + {THEMES.map((t) => ( + + ))}
diff --git a/src/components/views/CallGraphView.tsx b/src/components/views/CallGraphView.tsx new file mode 100644 index 0000000..4ef46cb --- /dev/null +++ b/src/components/views/CallGraphView.tsx @@ -0,0 +1,83 @@ +import { useState, useEffect } from 'react'; +import { GraphView } from './GraphView'; +import type { RizinInstance } from '@/lib/rizin'; + +interface CallGraphViewProps { + rizin: RizinInstance; + onSeek?: (address: number) => void; + className?: string; +} + +interface RawCallNode { + id?: number | string; + offset?: number; + addr?: number; + title?: string; + name?: string; + out_nodes?: Array; +} + +type GraphElements = { + nodes: { id: string; label: string; offset?: number }[]; + edges: { source: string; target: string; type?: 'call' }[]; +}; + +// agCj returns either a flat node array or a wrapper with a `nodes` array. +function buildCallGraph(data: unknown): GraphElements { + let blocks: RawCallNode[] = []; + if (Array.isArray(data)) { + const first = data[0] as { nodes?: RawCallNode[] } | undefined; + blocks = Array.isArray(first?.nodes) ? first!.nodes! : (data as RawCallNode[]); + } else if (data && typeof data === 'object') { + blocks = ((data as { nodes?: RawCallNode[] }).nodes) ?? []; + } + + const nodes = blocks.map((n, i) => ({ + id: String(n.id ?? n.offset ?? i), + label: n.title ?? n.name ?? `0x${Number(n.offset ?? 0).toString(16)}`, + offset: typeof n.offset === 'number' ? n.offset : typeof n.addr === 'number' ? n.addr : undefined, + })); + + const edges: GraphElements['edges'] = []; + for (const n of blocks) { + const source = String(n.id ?? n.offset ?? 0); + for (const target of Array.isArray(n.out_nodes) ? n.out_nodes : []) { + edges.push({ source, target: String(target), type: 'call' }); + } + } + return { nodes, edges }; +} + +export function CallGraphView({ rizin, onSeek, className }: CallGraphViewProps) { + const [elements, setElements] = useState({ nodes: [], edges: [] }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + setLoading(true); + rizin + .executeCommand('agCj') + .then((out) => { + if (cancelled) return; + try { + setElements(buildCallGraph(JSON.parse(out))); + } catch { + setElements({ nodes: [], edges: [] }); + } + }) + .catch(() => { + if (!cancelled) setElements({ nodes: [], edges: [] }); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, [rizin]); + + if (loading) { + return
Building call graph...
; + } + return ; +} diff --git a/src/components/views/DisassemblyView.tsx b/src/components/views/DisassemblyView.tsx index 764e16b..4521b32 100644 --- a/src/components/views/DisassemblyView.tsx +++ b/src/components/views/DisassemblyView.tsx @@ -1,30 +1,36 @@ -import { useRef, useEffect } from 'react'; +import { useRef, useEffect, useState } from 'react'; import { useUIStore, useSettingsStore } from '@/stores'; import { cn } from '@/lib/utils'; import { formatAddress } from '@/lib/utils/format'; import type { RzDisasmLine, RzReference } from '@/types/rizin'; -import { ScrollArea } from '@/components/ui'; -import { ChevronRight } from 'lucide-react'; +import { ScrollArea, Input } from '@/components/ui'; +import { ChevronRight, MessageSquare, Binary } from 'lucide-react'; interface DisassemblyViewProps { lines: RzDisasmLine[]; onNavigate?: (address: number) => void; + onComment?: (address: number, text: string) => void; + onShowInHex?: (address: number) => void; className?: string; } -export function DisassemblyView({ lines, onNavigate, className }: DisassemblyViewProps) { +export function DisassemblyView({ lines, onNavigate, onComment, onShowInHex, className }: DisassemblyViewProps) { const { currentAddress } = useUIStore(); const showLineNumbers = useSettingsStore((s) => s.showLineNumbers); const scrollRef = useRef(null); + const [editing, setEditing] = useState<{ addr: number; value: string } | null>(null); - // Keep the highlighted instruction in view as the seek address changes + // Keep the highlighted instruction in view as the seek address changes. useEffect(() => { - const container = scrollRef.current; - if (!container) return; - const row = container.querySelector(`[data-offset="${currentAddress}"]`); + const row = scrollRef.current?.querySelector(`[data-offset="${currentAddress}"]`); row?.scrollIntoView({ block: 'nearest' }); }, [currentAddress, lines]); + const submitComment = () => { + if (editing) onComment?.(editing.addr, editing.value); + setEditing(null); + }; + return (
@@ -32,7 +38,7 @@ export function DisassemblyView({ lines, onNavigate, className }: DisassemblyVie
Bytes
Instruction
- +
{lines.length === 0 ? ( @@ -45,7 +51,7 @@ export function DisassemblyView({ lines, onNavigate, className }: DisassemblyVie key={line.offset} data-offset={line.offset} className={cn( - 'group flex min-h-[1.5rem] px-4 py-0.5 text-sm hover:bg-accent/50 cursor-pointer transition-colors', + 'group flex min-h-[1.5rem] items-center px-4 py-0.5 text-sm hover:bg-accent/50 cursor-pointer transition-colors', line.offset === currentAddress && 'bg-primary/20 hover:bg-primary/25 border-l-2 border-primary' )} onClick={() => onNavigate?.(line.offset)} @@ -59,7 +65,7 @@ export function DisassemblyView({ lines, onNavigate, className }: DisassemblyVie
{line.bytes}
- +
{line.disasm.split(' ')[0]} @@ -67,13 +73,27 @@ export function DisassemblyView({ lines, onNavigate, className }: DisassemblyVie {line.disasm.split(' ').slice(1).join(' ')} - - {line.comment && ( - - ; {line.comment} - + + {editing?.addr === line.offset ? ( + e.stopPropagation()} + onChange={(e) => setEditing({ addr: line.offset, value: e.target.value })} + onBlur={submitComment} + onKeyDown={(e) => { + if (e.key === 'Enter') submitComment(); + else if (e.key === 'Escape') setEditing(null); + }} + placeholder="Comment (empty to clear)" + className="ml-4 inline-block h-5 w-64 text-[11px]" + /> + ) : ( + line.comment && ( + ; {line.comment} + ) )} - + {line.refs && line.refs.length > 0 && ( {line.refs.map((ref: RzReference, i: number) => ( @@ -84,7 +104,28 @@ export function DisassemblyView({ lines, onNavigate, className }: DisassemblyVie )}
- + +
+ {onComment && ( + + )} + {onShowInHex && ( + + )} +
+ {line.jump !== undefined && ( )} diff --git a/src/components/views/FlagsView.tsx b/src/components/views/FlagsView.tsx new file mode 100644 index 0000000..491f982 --- /dev/null +++ b/src/components/views/FlagsView.tsx @@ -0,0 +1,143 @@ +import { useState, useEffect, useCallback, useMemo } from 'react'; +import { toast } from 'sonner'; +import { cn } from '@/lib/utils'; +import { formatAddressShort } from '@/lib/utils/format'; +import { Input, Button, ScrollArea, Badge } from '@/components/ui'; +import { Flag, Plus, Trash2, Search, RefreshCw } from 'lucide-react'; +import type { RizinInstance } from '@/lib/rizin'; + +interface FlagEntry { + name: string; + offset: number; + size: number; +} + +interface FlagsViewProps { + rizin: RizinInstance; + onSeek?: (address: number) => void; + className?: string; +} + +const MAX_ROWS = 2000; + +export function FlagsView({ rizin, onSeek, className }: FlagsViewProps) { + const [flags, setFlags] = useState([]); + const [filter, setFilter] = useState(''); + const [newName, setNewName] = useState(''); + const [newAddr, setNewAddr] = useState(''); + + const refresh = useCallback(async () => { + try { + const out = await rizin.executeCommand('flj'); + const parsed = JSON.parse(out); + const list: FlagEntry[] = Array.isArray(parsed) + ? parsed + .map((f) => ({ name: String(f?.name ?? ''), offset: Number(f?.offset ?? 0), size: Number(f?.size ?? 0) })) + .filter((f) => f.name) + : []; + setFlags(list); + } catch { + setFlags([]); + } + }, [rizin]); + + useEffect(() => { void refresh(); }, [refresh]); + + const filtered = useMemo(() => { + const term = filter.trim().toLowerCase(); + if (!term) return flags; + return flags.filter((f) => f.name.toLowerCase().includes(term) || formatAddressShort(f.offset).includes(term)); + }, [flags, filter]); + + const addFlag = useCallback(async () => { + const name = newName.trim().replace(/[^A-Za-z0-9_.]/g, '_'); + const addr = newAddr.trim(); + if (!name || !addr) return; + await rizin.executeCommand(`f ${name} @ ${addr}`); + setNewName(''); + setNewAddr(''); + toast.success('Flag added'); + await refresh(); + }, [newName, newAddr, rizin, refresh]); + + const deleteFlag = useCallback(async (name: string) => { + await rizin.executeCommand(`f-${name}`); + await refresh(); + }, [rizin, refresh]); + + return ( +
+
+
+

+ + Flags + + {filtered.length.toLocaleString()}{filter && ` / ${flags.length.toLocaleString()}`} + +

+ +
+
+ + setFilter(e.target.value)} className="h-8 pl-8 text-xs" /> +
+
+ setNewName(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && addFlag()} + className="h-7 flex-1 text-[11px]" + /> + setNewAddr(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && addFlag()} + className="h-7 w-28 text-[11px]" + /> + +
+
+ + +
+ {filtered.length === 0 ? ( +

No flags

+ ) : ( + filtered.slice(0, MAX_ROWS).map((f) => ( +
onSeek?.(f.offset)} + className="group flex cursor-pointer items-center justify-between gap-2 rounded px-2 py-1 text-xs hover:bg-accent/50" + > + + + {f.name} + + + {formatAddressShort(f.offset)} + + +
+ )) + )} + {filtered.length > MAX_ROWS && ( +

Showing first {MAX_ROWS.toLocaleString()}. Refine the filter to see more.

+ )} +
+
+
+ ); +} diff --git a/src/components/views/FunctionsView.tsx b/src/components/views/FunctionsView.tsx index 0112fe1..421771d 100644 --- a/src/components/views/FunctionsView.tsx +++ b/src/components/views/FunctionsView.tsx @@ -5,23 +5,31 @@ import { cn } from '@/lib/utils'; import { formatAddressShort, formatSize } from '@/lib/utils/format'; import type { RzFunction } from '@/types/rizin'; import { Input, Badge } from '@/components/ui'; -import { Search, Hash, Box, ChevronUp, ChevronDown } from 'lucide-react'; +import { Search, Hash, Box, ChevronUp, ChevronDown, Pencil, Binary } from 'lucide-react'; interface FunctionsViewProps { functions: RzFunction[]; onSelect?: (fcn: RzFunction) => void; + onRename?: (offset: number, name: string) => void; + onShowInHex?: (offset: number) => void; className?: string; } const ROW_HEIGHT = 52; const OVERSCAN = 5; -export function FunctionsView({ functions, onSelect, className }: FunctionsViewProps) { +export function FunctionsView({ functions, onSelect, onRename, onShowInHex, className }: FunctionsViewProps) { const [filter, setFilter] = useState(''); const { selectedFunction } = useUIStore(); const containerRef = useRef(null); const [scrollTop, setScrollTop] = useState(0); const [containerHeight, setContainerHeight] = useState(400); + const [renaming, setRenaming] = useState<{ offset: number; value: string } | null>(null); + + const submitRename = useCallback(() => { + if (renaming) onRename?.(renaming.offset, renaming.value); + setRenaming(null); + }, [renaming, onRename]); const validFunctions = useMemo(() => { return functions.filter(f => f && typeof f.name === 'string'); @@ -126,8 +134,10 @@ export function FunctionsView({ functions, onSelect, className }: FunctionsViewP >
{visibleRows.map(({ index, data: fcn }) => ( - + )} + {onShowInHex && ( + + )} )}
- +
))}
diff --git a/src/components/views/GraphView.tsx b/src/components/views/GraphView.tsx index 4856bcb..d53f488 100644 --- a/src/components/views/GraphView.tsx +++ b/src/components/views/GraphView.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useMemo, useCallback } from 'react'; import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; import { useTheme } from '@/providers'; -import { cn } from '@/lib/utils'; +import { cn, cssVarHex } from '@/lib/utils'; import { Share2, ZoomIn, ZoomOut, Maximize } from 'lucide-react'; import { Button } from '@/components/ui'; @@ -30,15 +30,23 @@ interface GraphViewProps { className?: string; } -const palette = (dark: boolean) => ({ - nodeBg: dark ? '#1e293b' : '#f8fafc', - nodeText: dark ? '#e2e8f0' : '#0f172a', - nodeBorder: dark ? '#475569' : '#cbd5e1', - edge: dark ? '#475569' : '#cbd5e1', -}); +// Reads graph colors from active theme tokens so CFG matches every theme. +function readPalette() { + return { + nodeBg: cssVarHex('--card'), + nodeText: cssVarHex('--foreground'), + nodeBorder: cssVarHex('--border'), + edge: cssVarHex('--muted-foreground'), + entry: cssVarHex('--success'), + exit: cssVarHex('--warning'), + current: cssVarHex('--primary'), + fail: cssVarHex('--destructive'), + call: cssVarHex('--code-function'), + }; +} -function buildStylesheet(dark: boolean): cytoscape.StylesheetStyle[] { - const c = palette(dark); +function buildStylesheet(): cytoscape.StylesheetStyle[] { + const c = readPalette(); return [ { selector: 'node', @@ -64,15 +72,15 @@ function buildStylesheet(dark: boolean): cytoscape.StylesheetStyle[] { }, { selector: 'node[type="entry"]', - style: { 'border-color': '#22c55e', 'border-width': 3 }, + style: { 'border-color': c.entry, 'border-width': 3 }, }, { selector: 'node[type="exit"]', - style: { 'border-color': '#f59e0b' }, + style: { 'border-color': c.exit }, }, { selector: 'node.current', - style: { 'border-color': '#38bdf8', 'border-width': 4 }, + style: { 'border-color': c.current, 'border-width': 4 }, }, { selector: 'edge', @@ -84,11 +92,11 @@ function buildStylesheet(dark: boolean): cytoscape.StylesheetStyle[] { 'curve-style': 'bezier', }, }, - { selector: 'edge[type="jump"]', style: { 'line-color': '#22c55e', 'target-arrow-color': '#22c55e' } }, - { selector: 'edge[type="fail"]', style: { 'line-color': '#ef4444', 'target-arrow-color': '#ef4444' } }, + { selector: 'edge[type="jump"]', style: { 'line-color': c.entry, 'target-arrow-color': c.entry } }, + { selector: 'edge[type="fail"]', style: { 'line-color': c.fail, 'target-arrow-color': c.fail } }, { selector: 'edge[type="call"]', - style: { 'line-color': '#3b82f6', 'target-arrow-color': '#3b82f6', 'line-style': 'dashed' }, + style: { 'line-color': c.call, 'target-arrow-color': c.call, 'line-style': 'dashed' }, }, ]; } @@ -98,10 +106,7 @@ export function GraphView({ nodes, edges, currentAddress, onSeek, className }: G const cyRef = useRef(null); const onSeekRef = useRef(onSeek); onSeekRef.current = onSeek; - const { resolvedTheme } = useTheme(); - const dark = resolvedTheme === 'dark'; - const darkRef = useRef(dark); - darkRef.current = dark; + const { resolvedThemeId } = useTheme(); const elements = useMemo(() => { if (!nodes.length) return []; @@ -132,7 +137,7 @@ export function GraphView({ nodes, edges, currentAddress, onSeek, className }: G const cy = cytoscape({ container: containerRef.current, elements, - style: buildStylesheet(darkRef.current), + style: buildStylesheet(), layout: { name: 'dagre', rankDir: 'TB', nodeSep: 40, rankSep: 80 } as cytoscape.LayoutOptions, }); @@ -149,8 +154,8 @@ export function GraphView({ nodes, edges, currentAddress, onSeek, className }: G }, [elements]); useEffect(() => { - cyRef.current?.style(buildStylesheet(dark)); - }, [dark]); + cyRef.current?.style(buildStylesheet()); + }, [resolvedThemeId]); useEffect(() => { const cy = cyRef.current; diff --git a/src/components/views/HexView.tsx b/src/components/views/HexView.tsx index aa1589e..bc37977 100644 --- a/src/components/views/HexView.tsx +++ b/src/components/views/HexView.tsx @@ -1,16 +1,19 @@ import { useState, useMemo, useCallback, useRef, useEffect } from 'react'; -import { useUIStore, useSettingsStore } from '@/stores'; +import { toast } from 'sonner'; +import { useSettingsStore, useUIStore } from '@/stores'; import { cn } from '@/lib/utils'; import { formatAddress } from '@/lib/utils/format'; import type { RizinInstance } from '@/lib/rizin'; import { Input } from '@/components/ui'; -import { Search, ArrowUp, ArrowDown, ChevronDown, ChevronUp } from 'lucide-react'; +import { Search, ArrowUp, ArrowDown, ChevronDown, ChevronUp, Pencil, Binary } from 'lucide-react'; interface HexViewProps { rizin: RizinInstance; baseAddress: number; totalSize: number; className?: string; + writeMode?: boolean; + onAfterWrite?: () => void; } interface MemWindow { @@ -25,21 +28,39 @@ const MAX_HITS = 1000; const EMPTY = new Uint8Array(0); const EMPTY_WINDOW: MemWindow = { start: 0, length: 0, bytes: EMPTY }; -export function HexView({ rizin, baseAddress, totalSize, className }: HexViewProps) { +type SearchType = 'hex' | 'string' | 'int' | 'float'; +type StringEncoding = 'ascii' | 'utf8' | 'utf16le'; + +export function HexView({ rizin, baseAddress, totalSize, className, writeMode = false, onAfterWrite }: HexViewProps) { const { hexBytesPerRow } = useSettingsStore(); - const { currentAddress, setCurrentAddress } = useUIStore(); + const hexTarget = useUIStore((s) => s.hexTarget); + // The hex view addresses the raw file (physical offsets), so the cursor is + // local rather than the shared virtual-address seek. + const [cursor, setCursor] = useState(0); const containerRef = useRef(null); + const editInputRef = useRef(null); + const cancelEditRef = useRef(false); const scrollTopRef = useRef(0); const fetchSeqRef = useRef(0); const searchSeqRef = useRef(0); + const [editing, setEditing] = useState<{ addr: number; value: string } | null>(null); const [scrollTop, setScrollTop] = useState(0); const [containerHeight, setContainerHeight] = useState(600); const [memWindow, setMemWindow] = useState(EMPTY_WINDOW); const [searchQuery, setSearchQuery] = useState(''); + const [searchType, setSearchType] = useState('string'); + const [caseInsensitive, setCaseInsensitive] = useState(false); + const [encoding, setEncoding] = useState('ascii'); + const [intWidth, setIntWidth] = useState<1 | 2 | 4 | 8>(4); + const [floatWidth, setFloatWidth] = useState<32 | 64>(32); + const [bigEndian, setBigEndian] = useState(false); const [searchResults, setSearchResults] = useState([]); const [searchHitLen, setSearchHitLen] = useState(1); const [currentSearchIndex, setCurrentSearchIndex] = useState(0); const [searching, setSearching] = useState(false); + const [hasSearched, setHasSearched] = useState(false); + const [showInspector, setShowInspector] = useState(false); + const [inspectorBE, setInspectorBE] = useState(false); const bpr = hexBytesPerRow; const windowBytes = Math.min(Math.max(bpr * 256, 8192), 65536); @@ -80,7 +101,7 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro const seq = ++fetchSeqRef.current; rizin - .readMemory(startAddr, length) + .readFileSlice(startAddr, length) .then((bytes) => { if (seq !== fetchSeqRef.current) return; setMemWindow({ start: startAddr, length, bytes }); @@ -117,6 +138,34 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro return set; }, [searchResults, searchHitLen]); + // Interpret the bytes at the cursor as the common scalar types. + const inspector = useMemo(() => { + const offset = cursor - memWindow.start; + if (!showInspector || offset < 0 || offset >= memWindow.bytes.length) return null; + const slice = memWindow.bytes.subarray(offset, offset + 8); + const n = slice.length; + const buf = new Uint8Array(8); + buf.set(slice); + const dv = new DataView(buf.buffer); + const le = !inspectorBE; + const fmtFloat = (v: number) => (Number.isFinite(v) ? (Object.is(v, -0) ? '0' : Number(v.toPrecision(7)).toString()) : String(v)); + const rows: Array<{ label: string; value: string }> = [ + { label: 'int8', value: n >= 1 ? String(dv.getInt8(0)) : '—' }, + { label: 'uint8', value: n >= 1 ? String(dv.getUint8(0)) : '—' }, + { label: 'int16', value: n >= 2 ? String(dv.getInt16(0, le)) : '—' }, + { label: 'uint16', value: n >= 2 ? String(dv.getUint16(0, le)) : '—' }, + { label: 'int32', value: n >= 4 ? String(dv.getInt32(0, le)) : '—' }, + { label: 'uint32', value: n >= 4 ? String(dv.getUint32(0, le)) : '—' }, + { label: 'int64', value: n >= 8 ? dv.getBigInt64(0, le).toString() : '—' }, + { label: 'uint64', value: n >= 8 ? dv.getBigUint64(0, le).toString() : '—' }, + { label: 'float32', value: n >= 4 ? fmtFloat(dv.getFloat32(0, le)) : '—' }, + { label: 'float64', value: n >= 8 ? fmtFloat(dv.getFloat64(0, le)) : '—' }, + { label: 'char', value: n >= 1 && slice[0] >= 0x20 && slice[0] <= 0x7e ? `'${String.fromCharCode(slice[0])}'` : '—' }, + { label: 'binary', value: n >= 1 ? slice[0].toString(2).padStart(8, '0') : '—' }, + ]; + return rows; + }, [showInspector, inspectorBE, cursor, memWindow]); + useEffect(() => { const container = containerRef.current; if (!container) return; @@ -147,53 +196,99 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro const goToAddress = useCallback((addr: number) => { const row = Math.floor((addr - baseAddress) / bpr); scrollToRow(row); - setCurrentAddress(addr); - }, [baseAddress, bpr, scrollToRow, setCurrentAddress]); + setCursor(addr); + }, [baseAddress, bpr, scrollToRow, setCursor]); + + // Jump to a physical offset requested from another view ("show in hex"). + useEffect(() => { + if (hexTarget) goToAddress(hexTarget.offset); + // Re-run on each new request, even for the same offset. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hexTarget?.nonce]); useEffect(() => { - if (currentAddress < baseAddress || currentAddress >= baseAddress + totalSize) return; - const rowTop = Math.floor((currentAddress - baseAddress) / bpr) * ROW_HEIGHT; + if (cursor < baseAddress || cursor >= baseAddress + totalSize) return; + const rowTop = Math.floor((cursor - baseAddress) / bpr) * ROW_HEIGHT; const viewTop = scrollTopRef.current; if (rowTop < viewTop || rowTop + ROW_HEIGHT > viewTop + containerHeight) { - scrollToRow(Math.floor((currentAddress - baseAddress) / bpr)); + scrollToRow(Math.floor((cursor - baseAddress) / bpr)); + } + }, [cursor, baseAddress, totalSize, bpr, containerHeight, scrollToRow]); + + // Builds the byte pattern to search for over the raw file. Returns the needle + // plus a case-insensitive flag (ASCII only). null when the query is incomplete. + const buildNeedle = useCallback((): { needle: Uint8Array; caseInsensitive: boolean } | null => { + const q = searchQuery; + if (searchType === 'hex') { + const hex = q.replace(/[^0-9a-fA-F]/g, ''); + if (hex.length < 2) return null; + const even = hex.length % 2 === 0 ? hex : hex.slice(0, -1); + const bytes = new Uint8Array(even.length / 2); + for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(even.slice(i * 2, i * 2 + 2), 16); + return { needle: bytes, caseInsensitive: false }; + } + if (searchType === 'string') { + if (!q) return null; + let bytes: Uint8Array; + if (encoding === 'utf16le') { + bytes = new Uint8Array(q.length * 2); + for (let i = 0; i < q.length; i++) { + const c = q.charCodeAt(i); + bytes[i * 2] = c & 0xff; + bytes[i * 2 + 1] = (c >> 8) & 0xff; + } + } else if (encoding === 'utf8') { + bytes = new TextEncoder().encode(q); + } else { + bytes = new Uint8Array(q.length); + for (let i = 0; i < q.length; i++) bytes[i] = q.charCodeAt(i) & 0xff; + } + return { needle: bytes, caseInsensitive }; } - }, [currentAddress, baseAddress, totalSize, bpr, containerHeight, scrollToRow]); + if (searchType === 'int') { + const trimmed = q.trim(); + if (!/^-?(0x[0-9a-fA-F]+|\d+)$/.test(trimmed)) return null; + const mask = (1n << BigInt(intWidth * 8)) - 1n; + const u = BigInt(trimmed) & mask; + const bytes = new Uint8Array(intWidth); + const dv = new DataView(bytes.buffer); + if (intWidth === 1) dv.setUint8(0, Number(u)); + else if (intWidth === 2) dv.setUint16(0, Number(u), !bigEndian); + else if (intWidth === 4) dv.setUint32(0, Number(u), !bigEndian); + else dv.setBigUint64(0, u, !bigEndian); + return { needle: bytes, caseInsensitive: false }; + } + const num = Number(q); + if (!Number.isFinite(num) || q.trim() === '') return null; + const bytes = new Uint8Array(floatWidth / 8); + const dv = new DataView(bytes.buffer); + if (floatWidth === 32) dv.setFloat32(0, num, !bigEndian); + else dv.setFloat64(0, num, !bigEndian); + return { needle: bytes, caseInsensitive: false }; + }, [searchQuery, searchType, caseInsensitive, encoding, intWidth, floatWidth, bigEndian]); const handleSearch = useCallback(async () => { - const query = searchQuery.trim(); - if (!query) { + const built = buildNeedle(); + setHasSearched(true); + if (!built) { setSearchResults([]); return; } - - const compact = query.replace(/\s+/g, ''); - const looksHex = /^[0-9a-fA-F]+$/.test(compact) && compact.length >= 2; - const hex = looksHex ? compact.slice(0, compact.length - (compact.length % 2)) : ''; - const command = hex ? `/xj ${hex}` : `/j ${query}`; - const hitLen = hex ? hex.length / 2 : new TextEncoder().encode(query).length; - const seq = ++searchSeqRef.current; setSearching(true); try { - const output = await rizin.executeCommand(command); + const matches = await rizin.searchFileBytes(built.needle, built.caseInsensitive); if (seq !== searchSeqRef.current) return; - const parsed = JSON.parse(output); - const hits = Array.isArray(parsed) - ? parsed - .map((hit) => (hit && typeof hit.offset === 'number' ? hit.offset : null)) - .filter((offset): offset is number => offset != null) - .slice(0, MAX_HITS) - : []; - setSearchResults(hits); - setSearchHitLen(Math.max(1, hitLen)); + setSearchResults(matches.slice(0, MAX_HITS)); + setSearchHitLen(Math.max(1, built.needle.length)); setCurrentSearchIndex(0); - if (hits.length > 0) goToAddress(hits[0]); + if (matches.length > 0) goToAddress(matches[0]); } catch { if (seq === searchSeqRef.current) setSearchResults([]); } finally { if (seq === searchSeqRef.current) setSearching(false); } - }, [searchQuery, rizin, goToAddress]); + }, [buildNeedle, rizin, goToAddress]); const navigateResult = useCallback((direction: 1 | -1) => { if (searchResults.length === 0) return; @@ -202,6 +297,54 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro goToAddress(searchResults[next]); }, [searchResults, currentSearchIndex, goToAddress]); + const resetSearch = useCallback(() => { + setSearchResults([]); + setHasSearched(false); + }, []); + + // Locking editing discards any in-progress byte edit. + useEffect(() => { + if (!writeMode) setEditing(null); + }, [writeMode]); + + useEffect(() => { + if (editing) editInputRef.current?.focus(); + }, [editing]); + + const beginEdit = useCallback((addr: number, current: number | null) => { + if (!writeMode || current === null) return; + cancelEditRef.current = false; + setEditing({ addr, value: '' }); + }, [writeMode]); + + const cancelEdit = useCallback(() => { + cancelEditRef.current = true; + setEditing(null); + }, []); + + const commitEdit = useCallback(async () => { + // Escape sets this before the unmount blur fires, so skip the commit. + if (cancelEditRef.current) { + cancelEditRef.current = false; + return; + } + if (!editing) return; + const value = editing.value; + const addr = editing.addr; + setEditing(null); + if (value.length === 0) return; + + const result = await rizin.patchFile(addr, value); + if (!result.ok) { + toast.error(result.error ?? 'Write failed'); + return; + } + // Refetch the window so the patched byte shows immediately. + fetchSeqRef.current++; + setMemWindow(EMPTY_WINDOW); + onAfterWrite?.(); + }, [editing, rizin, onAfterWrite]); + const jumpToStart = useCallback(() => { if (containerRef.current) containerRef.current.scrollTop = 0; }, []); @@ -221,13 +364,22 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro return (
- + {totalSize.toLocaleString()} bytes • {totalRows.toLocaleString()} rows - + Row {Math.floor(scrollTop / ROW_HEIGHT) + 1} -
+ {writeMode && ( + + + Edit + + )} +
@@ -235,33 +387,116 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro
+
-
- - setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - className="pl-7 h-6 text-[11px] w-36" - /> +
+ + + {searchType === 'string' && ( + <> + + + + )} + {(searchType === 'int' || searchType === 'float') && ( + <> + + + + )} + +
+ + { setSearchQuery(e.target.value); resetSearch(); }} + onKeyDown={(e) => { + if (e.key !== 'Enter') return; + if (searchResults.length > 0) navigateResult(e.shiftKey ? -1 : 1); + else handleSearch(); + }} + className="pl-7 h-6 text-[11px] w-32" + /> +
+ + + {searching && } {!searching && searchResults.length > 0 && ( <> - + {currentSearchIndex + 1}/{searchResults.length} - - )} + {!searching && hasSearched && searchResults.length === 0 && ( + No matches + )}
+
+
Offset
Hex
@@ -271,7 +506,7 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro
{visibleRows.map((line) => { - const isCurrentRow = line.offset <= currentAddress && currentAddress < line.offset + bpr; + const isCurrentRow = line.offset <= cursor && cursor < line.offset + bpr; return (
setCurrentAddress(line.offset)} + onClick={() => setCursor(line.offset)} > {formatAddress(line.offset, addrBits)}
@@ -297,12 +532,32 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro
{line.bytes.map((byte, i) => { const byteAddr = line.offset + i; - const isCurrent = byteAddr === currentAddress; + const isCurrent = byteAddr === cursor; const isSearchHit = highlightSet.has(byteAddr); + if (editing && editing.addr === byteAddr) { + return ( + + setEditing({ addr: byteAddr, value: e.target.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 2) }) + } + onBlur={commitEdit} + onKeyDown={(e) => { + if (e.key === 'Enter') commitEdit(); + else if (e.key === 'Escape') cancelEdit(); + }} + className="w-5 text-center text-[11px] tabular-nums rounded-sm bg-primary text-primary-foreground outline-none ring-1 ring-primary-foreground/60 lowercase" + /> + ); + } return ( setCurrentAddress(byteAddr)} + onClick={() => setCursor(byteAddr)} + onDoubleClick={() => beginEdit(byteAddr, byte)} + title={writeMode && byte !== null ? 'Double-click to edit' : undefined} className={cn( 'w-5 text-center text-[11px] tabular-nums cursor-pointer rounded-sm', byte === null @@ -311,7 +566,8 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro ? 'text-muted-foreground/25' : 'text-foreground', isCurrent && 'bg-primary text-primary-foreground font-semibold', - isSearchHit && !isCurrent && 'bg-yellow-400/80 text-black' + isSearchHit && !isCurrent && 'bg-yellow-400/80 text-black', + writeMode && byte !== null && !isCurrent && 'hover:bg-primary/30' )} > {byte === null ? '··' : byte.toString(16).padStart(2, '0')} @@ -323,18 +579,64 @@ export function HexView({ rizin, baseAddress, totalSize, className }: HexViewPro ))}
-
- {line.bytes.map((b) => (b !== null && b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : '·')).join('')} +
+ {line.bytes.map((byte, i) => { + const byteAddr = line.offset + i; + const isCurrent = byteAddr === cursor; + const isSearchHit = highlightSet.has(byteAddr); + const printable = byte !== null && byte >= 0x20 && byte <= 0x7e; + return ( + setCursor(byteAddr)} + className={cn( + 'cursor-pointer', + !printable && 'text-muted-foreground/40', + isCurrent && 'bg-primary text-primary-foreground', + isSearchHit && !isCurrent && 'bg-yellow-400/80 text-black' + )} + > + {printable ? String.fromCharCode(byte as number) : '·'} + + ); + })}
); })}
+
+ + {showInspector && inspector && ( + + )} +
Viewing {visibleRowCount} of {totalRows.toLocaleString()} rows - Current: {formatAddress(currentAddress, addrBits)} + Current: {formatAddress(cursor, addrBits)}
); diff --git a/src/components/views/ScriptsView.tsx b/src/components/views/ScriptsView.tsx new file mode 100644 index 0000000..01f59de --- /dev/null +++ b/src/components/views/ScriptsView.tsx @@ -0,0 +1,264 @@ +import { useState, useCallback, useEffect, useRef, type ChangeEvent } from 'react'; +import { EditorView, keymap, lineNumbers } from '@codemirror/view'; +import { EditorState, Compartment } from '@codemirror/state'; +import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands'; +import { foldGutter, foldKeymap, bracketMatching, indentOnInput } from '@codemirror/language'; +import { autocompletion, completionKeymap } from '@codemirror/autocomplete'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import { toast } from 'sonner'; +import { useScriptStore, useSettingsStore, type SavedScript } from '@/stores'; +import { useTheme } from '@/providers'; +import { cn, stripAnsi } from '@/lib/utils'; +import { languageExtension, editorTheme, completionSource, languageOf } from '@/lib/codemirror'; +import { Button, Input, ScrollArea } from '@/components/ui'; +import { Play, Save, Plus, Trash2, FileCode, Upload, Download } from 'lucide-react'; +import type { RizinInstance } from '@/lib/rizin'; + +interface ScriptsViewProps { + rizin: RizinInstance; + className?: string; +} + +export function ScriptsView({ rizin, className }: ScriptsViewProps) { + const { scripts, upsertScript, deleteScript } = useScriptStore(); + const { terminalAutocompleteMinChars, terminalAutocompleteMaxResults } = useSettingsStore(); + const { resolvedThemeId, resolvedTheme } = useTheme(); + + const editorParentRef = useRef(null); + const viewRef = useRef(null); + const langCompartment = useRef(new Compartment()); + const themeCompartment = useRef(new Compartment()); + const completionCompartment = useRef(new Compartment()); + const uploadRef = useRef(null); + + const [name, setName] = useState(''); + const [output, setOutput] = useState(''); + const [running, setRunning] = useState(false); + const [activeId, setActiveId] = useState(null); + + const language = languageOf(name || '.rz'); + + // Stable refs so editor keybindings always call the latest handlers. + const runRef = useRef<() => void>(() => {}); + const saveRef = useRef<() => void>(() => {}); + + const getSource = useCallback(() => viewRef.current?.state.doc.toString() ?? '', []); + + const setSource = useCallback((text: string) => { + const view = viewRef.current; + if (!view) return; + view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: text } }); + }, []); + + // Create the editor once. + useEffect(() => { + if (!editorParentRef.current) return; + const dark = resolvedTheme === 'dark'; + const state = EditorState.create({ + doc: '# One rizin command per line. Lines starting with # are comments.\nafl\npdf @ main', + extensions: [ + lineNumbers(), + foldGutter(), + history(), + bracketMatching(), + indentOnInput(), + highlightSelectionMatches(), + langCompartment.current.of(languageExtension('rz')), + completionCompartment.current.of( + autocompletion({ + override: [completionSource('rz', rizin.getCommandCatalog(), terminalAutocompleteMinChars)], + maxRenderedOptions: terminalAutocompleteMaxResults, + }) + ), + themeCompartment.current.of(editorTheme(dark)), + keymap.of([ + ...defaultKeymap, + ...historyKeymap, + ...searchKeymap, + ...completionKeymap, + ...foldKeymap, + indentWithTab, + { key: 'Mod-Enter', run: () => { runRef.current(); return true; } }, + { key: 'Mod-s', preventDefault: true, run: () => { saveRef.current(); return true; } }, + ]), + ], + }); + const view = new EditorView({ state, parent: editorParentRef.current }); + viewRef.current = view; + return () => { + view.destroy(); + viewRef.current = null; + }; + }, []); + + useEffect(() => { + viewRef.current?.dispatch({ + effects: themeCompartment.current.reconfigure(editorTheme(resolvedTheme === 'dark')), + }); + }, [resolvedThemeId, resolvedTheme]); + + useEffect(() => { + viewRef.current?.dispatch({ + effects: [ + langCompartment.current.reconfigure(languageExtension(language)), + completionCompartment.current.reconfigure( + autocompletion({ + override: [completionSource(language, rizin.getCommandCatalog(), terminalAutocompleteMinChars)], + maxRenderedOptions: terminalAutocompleteMaxResults, + }) + ), + ], + }); + }, [language, rizin, terminalAutocompleteMinChars, terminalAutocompleteMaxResults]); + + const loadScript = useCallback((script: SavedScript) => { + setActiveId(script.id); + setName(script.name); + setSource(script.content); + }, [setSource]); + + const newScript = useCallback(() => { + setActiveId(null); + setName(''); + setSource(''); + setOutput(''); + }, [setSource]); + + const run = useCallback(async () => { + if (running) return; + setRunning(true); + try { + const result = await rizin.runScript(getSource(), language); + const text = stripAnsi(result.output); + setOutput(result.error ? `${text}${text ? '\n' : ''}Error: ${result.error}` : text || '(no output)'); + } catch (error) { + setOutput(`Error: ${error instanceof Error ? error.message : String(error)}`); + } finally { + setRunning(false); + } + }, [getSource, language, rizin, running]); + + const save = useCallback(() => { + const script = upsertScript(name, getSource()); + setActiveId(script.id); + setName(script.name); + toast.success('Script saved'); + }, [name, getSource, upsertScript]); + + runRef.current = run; + saveRef.current = save; + + const handleDelete = useCallback((id: string) => { + deleteScript(id); + if (activeId === id) newScript(); + }, [deleteScript, activeId, newScript]); + + const handleUpload = useCallback(async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + event.target.value = ''; + if (!file) return; + const text = await file.text(); + setActiveId(null); + setName(file.name); + setSource(text); + }, [setSource]); + + const download = useCallback(() => { + const blob = new Blob([getSource()], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = (name.trim() || 'script') + (languageOf(name) === 'js' ? '' : name.includes('.') ? '' : '.rz'); + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + }, [getSource, name]); + + return ( +
+ + +
+
+ setName(e.target.value)} + placeholder="script.rz" + className="h-6 max-w-[180px] text-[11px]" + /> + + {language === 'js' ? 'JavaScript' : 'Rizin'} + +
+ + + + + +
+ +
+ +
+
+ Output + {output && ( + + )} +
+ +
{output}
+
+
+
+
+ ); +} diff --git a/src/components/views/index.ts b/src/components/views/index.ts index a6d9ed4..4008ccd 100644 --- a/src/components/views/index.ts +++ b/src/components/views/index.ts @@ -9,3 +9,5 @@ export { SectionsView } from './SectionsView'; export { HeaderInfoPanel } from './HeaderInfoPanel'; export { XrefsView } from './XrefsView'; export { DecompilerView } from './DecompilerView'; +export { FlagsView } from './FlagsView'; +export { CallGraphView } from './CallGraphView'; diff --git a/src/index.css b/src/index.css index f03e0ed..ed67d9a 100644 --- a/src/index.css +++ b/src/index.css @@ -3,77 +3,259 @@ @tailwind utilities; @layer base { + /* Default tokens (Rizin Dark). A concrete theme is applied via data-theme. */ :root { + --radius: 0.5rem; + + --background: 218 27% 9%; + --foreground: 213 27% 90%; + --card: 218 24% 12%; + --card-foreground: 213 27% 90%; + --popover: 218 26% 11%; + --popover-foreground: 213 27% 90%; + --primary: 199 89% 50%; + --primary-foreground: 213 40% 98%; + --secondary: 217 20% 18%; + --secondary-foreground: 213 27% 90%; + --muted: 217 20% 16%; + --muted-foreground: 215 16% 62%; + --accent: 217 22% 22%; + --accent-foreground: 213 27% 92%; + --destructive: 0 72% 51%; + --destructive-foreground: 0 0% 98%; + --success: 142 70% 45%; + --success-foreground: 0 0% 98%; + --warning: 38 92% 55%; + --warning-foreground: 222 47% 11%; + --border: 217 19% 20%; + --input: 217 19% 22%; + --ring: 199 89% 50%; + + --code-keyword: 265 85% 78%; + --code-string: 95 45% 64%; + --code-number: 32 95% 62%; + --code-comment: 220 12% 52%; + --code-function: 199 89% 66%; + --code-operator: 213 15% 75%; + --code-register: 340 80% 72%; + --code-address: 32 95% 60%; + --code-instruction: 199 89% 66%; + } + + :root[data-theme='midnight'] { + --background: 230 42% 7%; + --foreground: 220 30% 92%; + --card: 230 38% 10%; + --card-foreground: 220 30% 92%; + --popover: 230 40% 9%; + --popover-foreground: 220 30% 92%; + --primary: 190 95% 52%; + --primary-foreground: 230 45% 8%; + --secondary: 230 30% 17%; + --secondary-foreground: 220 30% 92%; + --muted: 230 30% 15%; + --muted-foreground: 222 18% 64%; + --accent: 250 50% 26%; + --accent-foreground: 220 30% 95%; + --destructive: 350 80% 58%; + --destructive-foreground: 0 0% 98%; + --success: 152 70% 48%; + --success-foreground: 0 0% 98%; + --warning: 40 95% 58%; + --warning-foreground: 230 45% 8%; + --border: 230 28% 18%; + --input: 230 28% 20%; + --ring: 190 95% 52%; + + --code-keyword: 250 90% 78%; + --code-string: 160 65% 60%; + --code-number: 40 95% 64%; + --code-comment: 228 16% 52%; + --code-function: 190 95% 68%; + --code-operator: 220 18% 78%; + --code-register: 320 85% 74%; + --code-address: 40 95% 62%; + --code-instruction: 190 95% 68%; + } + + :root[data-theme='nord'] { + --background: 220 16% 22%; + --foreground: 218 27% 88%; + --card: 222 16% 25%; + --card-foreground: 218 27% 88%; + --popover: 222 16% 24%; + --popover-foreground: 218 27% 88%; + --primary: 193 43% 67%; + --primary-foreground: 220 28% 16%; + --secondary: 220 17% 30%; + --secondary-foreground: 218 27% 88%; + --muted: 220 17% 28%; + --muted-foreground: 219 18% 70%; + --accent: 213 32% 52%; + --accent-foreground: 218 27% 94%; + --destructive: 354 42% 56%; + --destructive-foreground: 0 0% 98%; + --success: 92 28% 65%; + --success-foreground: 220 28% 16%; + --warning: 40 71% 73%; + --warning-foreground: 220 28% 16%; + --border: 220 17% 32%; + --input: 220 17% 34%; + --ring: 193 43% 67%; + + --code-keyword: 213 32% 62%; + --code-string: 92 28% 65%; + --code-number: 311 20% 70%; + --code-comment: 220 15% 55%; + --code-function: 179 25% 65%; + --code-operator: 218 27% 80%; + --code-register: 354 42% 64%; + --code-address: 311 20% 70%; + --code-instruction: 179 25% 65%; + } + + :root[data-theme='dracula'] { + --background: 231 15% 18%; + --foreground: 60 30% 96%; + --card: 232 14% 22%; + --card-foreground: 60 30% 96%; + --popover: 232 14% 21%; + --popover-foreground: 60 30% 96%; + --primary: 265 89% 78%; + --primary-foreground: 231 15% 12%; + --secondary: 232 14% 28%; + --secondary-foreground: 60 30% 96%; + --muted: 232 14% 26%; + --muted-foreground: 225 20% 70%; + --accent: 326 100% 74%; + --accent-foreground: 231 15% 12%; + --destructive: 0 100% 67%; + --destructive-foreground: 0 0% 100%; + --success: 135 94% 65%; + --success-foreground: 231 15% 12%; + --warning: 65 92% 76%; + --warning-foreground: 231 15% 12%; + --border: 232 14% 30%; + --input: 232 14% 32%; + --ring: 265 89% 78%; + + --code-keyword: 326 100% 74%; + --code-string: 65 92% 76%; + --code-number: 265 89% 80%; + --code-comment: 225 27% 58%; + --code-function: 135 94% 65%; + --code-operator: 60 30% 90%; + --code-register: 31 100% 71%; + --code-address: 31 100% 71%; + --code-instruction: 191 97% 77%; + } + + :root[data-theme='carbon'] { + --background: 0 0% 7%; + --foreground: 0 0% 88%; + --card: 0 0% 10%; + --card-foreground: 0 0% 88%; + --popover: 0 0% 9%; + --popover-foreground: 0 0% 88%; + --primary: 35 95% 55%; + --primary-foreground: 0 0% 8%; + --secondary: 0 0% 16%; + --secondary-foreground: 0 0% 88%; + --muted: 0 0% 14%; + --muted-foreground: 0 0% 58%; + --accent: 0 0% 20%; + --accent-foreground: 0 0% 92%; + --destructive: 4 80% 58%; + --destructive-foreground: 0 0% 98%; + --success: 142 65% 48%; + --success-foreground: 0 0% 8%; + --warning: 45 95% 55%; + --warning-foreground: 0 0% 8%; + --border: 0 0% 18%; + --input: 0 0% 20%; + --ring: 35 95% 55%; + + --code-keyword: 35 95% 62%; + --code-string: 142 50% 62%; + --code-number: 28 95% 64%; + --code-comment: 0 0% 46%; + --code-function: 200 80% 66%; + --code-operator: 0 0% 78%; + --code-register: 330 75% 70%; + --code-address: 35 95% 60%; + --code-instruction: 200 80% 66%; + } + + :root[data-theme='rizin-light'] { --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --foreground: 222 30% 14%; + --card: 210 33% 99%; + --card-foreground: 222 30% 14%; --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - --primary: 199 89% 48%; - --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - --success: 142 76% 36%; - --success-foreground: 210 40% 98%; - --warning: 38 92% 50%; - --warning-foreground: 222.2 47.4% 11.2%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 199 89% 48%; - --radius: 0.5rem; + --popover-foreground: 222 30% 14%; + --primary: 199 89% 44%; + --primary-foreground: 0 0% 100%; + --secondary: 210 30% 95%; + --secondary-foreground: 222 30% 20%; + --muted: 210 30% 95%; + --muted-foreground: 215 16% 42%; + --accent: 210 36% 93%; + --accent-foreground: 222 30% 18%; + --destructive: 0 75% 50%; + --destructive-foreground: 0 0% 100%; + --success: 142 70% 34%; + --success-foreground: 0 0% 100%; + --warning: 32 92% 45%; + --warning-foreground: 0 0% 100%; + --border: 214 24% 88%; + --input: 214 24% 86%; + --ring: 199 89% 44%; + + --code-keyword: 271 70% 50%; + --code-string: 142 60% 32%; + --code-number: 25 90% 42%; + --code-comment: 215 14% 50%; + --code-function: 199 89% 40%; + --code-operator: 0 0% 35%; + --code-register: 340 70% 48%; + --code-address: 25 90% 42%; + --code-instruction: 199 89% 40%; + } - --code-keyword: 271 81% 56%; - --code-string: 141 71% 36%; - --code-number: 27 96% 44%; - --code-comment: 215 14% 54%; - --code-function: 199 89% 48%; - --code-operator: 0 0% 40%; - --code-register: 340 82% 52%; - --code-address: 27 96% 44%; - --code-instruction: 199 89% 48%; - } - - .dark { - --background: 224 71% 4%; - --foreground: 213 31% 91%; - --card: 224 71% 4%; - --card-foreground: 213 31% 91%; - --popover: 224 71% 4%; - --popover-foreground: 213 31% 91%; - --primary: 199 89% 48%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 215 28% 17%; - --secondary-foreground: 213 31% 91%; - --muted: 215 28% 17%; - --muted-foreground: 215 20% 65%; - --accent: 215 28% 17%; - --accent-foreground: 213 31% 91%; - --destructive: 0 63% 31%; - --destructive-foreground: 210 40% 98%; - --success: 142 76% 36%; - --success-foreground: 210 40% 98%; - --warning: 38 92% 50%; - --warning-foreground: 222.2 47.4% 11.2%; - --border: 215 28% 17%; - --input: 215 28% 17%; - --ring: 199 89% 48%; - - --code-keyword: 271 91% 65%; - --code-string: 95 38% 62%; - --code-number: 32 98% 56%; - --code-comment: 215 14% 54%; - --code-function: 187 92% 69%; - --code-operator: 0 0% 80%; - --code-register: 340 95% 70%; - --code-address: 32 98% 56%; - --code-instruction: 187 92% 69%; + :root[data-theme='solarized-light'] { + --background: 44 87% 94%; + --foreground: 196 13% 35%; + --card: 46 42% 90%; + --card-foreground: 196 13% 35%; + --popover: 44 60% 92%; + --popover-foreground: 196 13% 35%; + --primary: 205 69% 49%; + --primary-foreground: 44 87% 96%; + --secondary: 46 33% 86%; + --secondary-foreground: 196 18% 30%; + --muted: 46 33% 86%; + --muted-foreground: 195 12% 48%; + --accent: 175 35% 80%; + --accent-foreground: 196 18% 26%; + --destructive: 1 71% 52%; + --destructive-foreground: 44 87% 96%; + --success: 68 75% 30%; + --success-foreground: 44 87% 96%; + --warning: 45 100% 32%; + --warning-foreground: 44 87% 96%; + --border: 46 25% 80%; + --input: 46 25% 78%; + --ring: 205 69% 49%; + + --code-keyword: 68 100% 28%; + --code-string: 175 59% 36%; + --code-number: 18 80% 44%; + --code-comment: 180 7% 56%; + --code-function: 205 69% 45%; + --code-operator: 196 13% 40%; + --code-register: 331 64% 52%; + --code-address: 18 80% 44%; + --code-instruction: 205 69% 45%; } } @@ -111,7 +293,7 @@ .scrollbar-thin { scrollbar-width: thin; - scrollbar-color: hsl(var(--muted)) transparent; + scrollbar-color: hsl(var(--muted-foreground) / 0.4) transparent; } .scrollbar-thin::-webkit-scrollbar { @@ -124,12 +306,12 @@ } .scrollbar-thin::-webkit-scrollbar-thumb { - background-color: hsl(var(--muted)); + background-color: hsl(var(--muted-foreground) / 0.35); border-radius: 4px; } .scrollbar-thin::-webkit-scrollbar-thumb:hover { - background-color: hsl(var(--muted-foreground)); + background-color: hsl(var(--muted-foreground) / 0.6); } .scrollbar-hidden { @@ -222,6 +404,6 @@ } .xterm-viewport::-webkit-scrollbar-thumb { - background-color: hsl(var(--muted)); + background-color: hsl(var(--muted-foreground) / 0.35); border-radius: 4px; } diff --git a/src/lib/codemirror.ts b/src/lib/codemirror.ts new file mode 100644 index 0000000..11d0273 --- /dev/null +++ b/src/lib/codemirror.ts @@ -0,0 +1,125 @@ +import { StreamLanguage, HighlightStyle, syntaxHighlighting } from '@codemirror/language'; +import { EditorView } from '@codemirror/view'; +import type { Extension } from '@codemirror/state'; +import { tags as t } from '@lezer/highlight'; +import { javascript } from '@codemirror/lang-javascript'; +import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; +import { cssVarHex } from '@/lib/utils'; +import type { RizinCommandHelpEntry } from '@/lib/rizin'; + +export type ScriptLanguage = 'rz' | 'js'; + +export function languageOf(name: string): ScriptLanguage { + return name.trim().toLowerCase().endsWith('.js') ? 'js' : 'rz'; +} + +// Lightweight highlighter for rizin cmd scripts: comments, cmd words, +// addrs, operators (@ seek, ~ grep, | pipe), strs, and $vars. +const rizinStream = StreamLanguage.define<{ start: boolean }>({ + startState: () => ({ start: true }), + token(stream, state) { + if (stream.eatSpace()) return null; + if (state.start && stream.peek() === '#') { + stream.skipToEnd(); + return 'comment'; + } + const atStart = state.start; + state.start = false; + if (stream.match(/^0x[0-9a-fA-F]+/)) return 'number'; + if (stream.match(/^\$[a-zA-Z0-9_]+/)) return 'variableName'; + if (stream.match(/^"(?:[^"\\]|\\.)*"/)) return 'string'; + if (stream.match(/^;/)) { + state.start = true; + return 'operator'; + } + if (stream.match(/^(@@|@|~|\||>|<)/)) return 'operator'; + if (atStart && stream.match(/^[a-zA-Z._?!/\\][a-zA-Z0-9._?!]*/)) return 'keyword'; + if (stream.match(/^[a-zA-Z._][a-zA-Z0-9._]*/)) return 'atom'; + if (stream.match(/^\d+/)) return 'number'; + stream.next(); + return null; + }, +}); + +export function languageExtension(language: ScriptLanguage): Extension { + return language === 'js' ? javascript() : rizinStream; +} + +// always matches app theme. +export function editorTheme(dark: boolean): Extension { + const highlight = HighlightStyle.define([ + { tag: t.comment, color: cssVarHex('--code-comment'), fontStyle: 'italic' }, + { tag: [t.keyword, t.operatorKeyword], color: cssVarHex('--code-keyword') }, + { tag: [t.string, t.special(t.string)], color: cssVarHex('--code-string') }, + { tag: [t.number, t.bool, t.null], color: cssVarHex('--code-number') }, + { tag: [t.operator, t.punctuation], color: cssVarHex('--code-operator') }, + { tag: [t.variableName, t.propertyName], color: cssVarHex('--code-function') }, + { tag: [t.function(t.variableName), t.definition(t.variableName)], color: cssVarHex('--code-function') }, + { tag: [t.atom, t.labelName], color: cssVarHex('--code-register') }, + { tag: [t.typeName, t.className, t.tagName], color: cssVarHex('--code-register') }, + ]); + + const view = EditorView.theme( + { + '&': { backgroundColor: cssVarHex('--background'), color: cssVarHex('--foreground'), height: '100%' }, + '.cm-content': { fontFamily: 'JetBrains Mono, Fira Code, Consolas, monospace', caretColor: cssVarHex('--primary') }, + '.cm-cursor, .cm-dropCursor': { borderLeftColor: cssVarHex('--primary') }, + '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { + backgroundColor: cssVarHex('--accent'), + }, + '.cm-gutters': { backgroundColor: cssVarHex('--muted'), color: cssVarHex('--muted-foreground'), border: 'none' }, + '.cm-activeLineGutter': { backgroundColor: cssVarHex('--accent') }, + '.cm-activeLine': { backgroundColor: 'transparent' }, + '.cm-tooltip': { + backgroundColor: cssVarHex('--popover'), + color: cssVarHex('--popover-foreground'), + border: `1px solid ${cssVarHex('--border')}`, + }, + '.cm-tooltip-autocomplete ul li[aria-selected]': { + backgroundColor: cssVarHex('--primary'), + color: cssVarHex('--primary-foreground'), + }, + }, + { dark } + ); + + return [view, syntaxHighlighting(highlight)]; +} + +const RZ_API: Completion[] = [ + { label: 'rz.cmd', type: 'method', detail: '(cmd)', info: 'Run a rizin command, returns its string output' }, + { label: 'rz.cmdj', type: 'method', detail: '(cmd)', info: 'Run a command and JSON.parse its output' }, + { label: 'rz.call', type: 'method', detail: '(cmd)', info: 'Run a command without shell parsing' }, + { label: 'rz.callj', type: 'method', detail: '(cmd)', info: 'Like cmdj, using call' }, + { label: 'rz.cmdAt', type: 'method', detail: '(cmd, at)', info: 'Run a command at an address' }, + { label: 'rz.log', type: 'method', detail: '(...args)', info: 'Print to the output console' }, +]; + +function jsCompletion(context: CompletionContext): CompletionResult | null { + const dotted = context.matchBefore(/rz\.\w*/); + if (dotted) { + return { from: dotted.from, options: RZ_API, validFor: /^rz\.\w*$/ }; + } + const word = context.matchBefore(/\w+/); + if (word && word.from !== word.to && /^r/i.test(word.text)) { + return { from: word.from, options: [{ label: 'rz', type: 'variable', info: 'Synchronous rizin command API' }], validFor: /^\w*$/ }; + } + return null; +} + +// Autocomplete: JS rz API (what we alr used). +export function completionSource(language: ScriptLanguage, catalog: Record, minChars: number) { + if (language === 'js') { + return jsCompletion; + } + const options: Completion[] = Object.values(catalog) + .filter((entry) => entry.name) + .map((entry) => ({ label: entry.name, type: 'keyword', detail: entry.summary, info: entry.description || entry.args })); + const floor = Math.max(1, minChars); + return (context: CompletionContext): CompletionResult | null => { + const word = context.matchBefore(/[a-zA-Z._?!/\\][\w._?!]*/); + if (!word) return null; + if (!context.explicit && word.to - word.from < floor) return null; + return { from: word.from, options, validFor: /^[\w._?!/\\]*$/ }; + }; +} diff --git a/src/lib/rizin/RizinInstance.ts b/src/lib/rizin/RizinInstance.ts index 143a395..1516cd4 100644 --- a/src/lib/rizin/RizinInstance.ts +++ b/src/lib/rizin/RizinInstance.ts @@ -49,9 +49,12 @@ export class RizinInstance { private _cacheHit = false; private notices: RizinNotice[] = []; private lastStderr = ''; + private _writeMode = false; + private _isDirty = false; private noticeCallbacks: Array<(notice: RizinNotice) => void> = []; private analysisCallbacks: Array<() => void> = []; + private stateCallbacks: Array<() => void> = []; constructor(worker: Worker) { this.worker = worker; @@ -71,6 +74,14 @@ export class RizinInstance { return this._cacheHit; } + get isWriteMode(): boolean { + return this._writeMode; + } + + get isDirty(): boolean { + return this._isDirty; + } + get allNotices(): RizinNotice[] { return [...this.notices]; } @@ -131,6 +142,33 @@ export class RizinInstance { this.commandCatalog = result.commandCatalog; } + async setWriteMode(enable: boolean): Promise { + const result = await this.send<'setWriteMode'>({ id: ++this.nextId, method: 'setWriteMode', enable }); + return result.writeMode; + } + + async readFileSlice(offset: number, length: number): Promise { + const result = await this.send<'readFileSlice'>({ id: ++this.nextId, method: 'readFileSlice', offset, length }); + return result.bytes; + } + + async searchFileBytes(needle: Uint8Array, caseInsensitive: boolean): Promise { + const result = await this.send<'searchFileBytes'>({ id: ++this.nextId, method: 'searchFileBytes', needle, caseInsensitive }); + return result.matches; + } + + async patchFile(offset: number, hex: string): Promise<{ ok: boolean; error?: string }> { + return this.send<'patchFile'>({ id: ++this.nextId, method: 'patchFile', offset, hex }); + } + + async exportBinary(): Promise<{ data: Uint8Array; name: string }> { + return this.send<'exportBinary'>({ id: ++this.nextId, method: 'exportBinary' }); + } + + async runScript(source: string, language: 'rz' | 'js'): Promise<{ output: string; error?: string }> { + return this.send<'runScript'>({ id: ++this.nextId, method: 'runScript', source, language }); + } + getCommandCatalog(): Record { return this.commandCatalog; } @@ -157,6 +195,14 @@ export class RizinInstance { }; } + // Fires after each state snapshot so the UI can track write-mode and dirty flags. + onStateChanged(callback: () => void): () => void { + this.stateCallbacks.push(callback); + return () => { + this.stateCallbacks = this.stateCallbacks.filter(cb => cb !== callback); + }; + } + async close(): Promise { if (this.disposed) return; try { @@ -217,6 +263,9 @@ export class RizinInstance { this._cacheHit = state.cacheHit; this.notices = state.notices; this.lastStderr = state.lastStderr; + this._writeMode = state.writeMode; + this._isDirty = state.isDirty; + this.stateCallbacks.forEach(cb => cb()); } private dispose(): void { @@ -227,5 +276,6 @@ export class RizinInstance { this.pending.clear(); this.noticeCallbacks = []; this.analysisCallbacks = []; + this.stateCallbacks = []; } } diff --git a/src/lib/rizin/analysisCache.ts b/src/lib/rizin/analysisCache.ts index 4d2aad5..e6f1b2f 100644 --- a/src/lib/rizin/analysisCache.ts +++ b/src/lib/rizin/analysisCache.ts @@ -168,7 +168,7 @@ export async function setCachedAnalysis(entry: CachedAnalysis): Promise { await db.put(STORE_NAME, stored); await evictIfNeeded(db); } catch { - // IndexedDB writes are best-effort; analysis can still continue without a cache write. + // IndexedDB writes are best-effort. Analysis still continues without a cache write. } } @@ -263,6 +263,6 @@ export async function removeCachedAnalysis(hash: string): Promise { const db = await getDB(); await deleteEntry(db, hash); } catch { - // Ignore cache-delete failures; stale entries will be skipped when detected later. + // Ignore delete failures. Stale entries are skipped when detected later. } } diff --git a/src/lib/rizin/projectBundle.ts b/src/lib/rizin/projectBundle.ts index eccedc1..f766983 100644 --- a/src/lib/rizin/projectBundle.ts +++ b/src/lib/rizin/projectBundle.ts @@ -1,8 +1,7 @@ -// Self-contained RzWeb project bundle: a Rizin `.rzdb` does NOT embed the -// binary's bytes — it only references the file's path — so a bare `.rzdb` can't -// be reopened cold in the browser. We wrap the binary together with the rzdb so -// loading a saved project is a single, fully cold action: no need to first open -// the matching binary by hand. +// Self-contained RzWeb project bundle. A Rizin `.rzdb` only references the +// binary's path, not its bytes, so a bare `.rzdb` cannot be reopened cold in +// the browser. We wrap the binary with the rzdb so loading a saved project is a +// single cold action. // // Layout (all integers little-endian): // magic "RZWEBPRJ" (8 bytes) @@ -12,7 +11,7 @@ // rzdbLen u32 + rzdb bytes // // decodeProjectBundle returns null for anything that isn't a bundle (e.g. a raw -// Rizin `.rzdb`), so callers can transparently fall back to the legacy path. +// Rizin `.rzdb`), so callers can fall back to the legacy path. const MAGIC = 'RZWEBPRJ'; const MAGIC_LEN = 8; diff --git a/src/lib/rizin/rizin.worker.ts b/src/lib/rizin/rizin.worker.ts index f08004c..1f1df4d 100644 --- a/src/lib/rizin/rizin.worker.ts +++ b/src/lib/rizin/rizin.worker.ts @@ -13,7 +13,7 @@ import type { } from './rizinProtocol'; // Set VITE_WASM_BASE_URL to point the worker at a local or preview rzwasi -// build (e.g. one that bundles the jsdec decompiler); defaults to the hosted CDN. +// build (e.g. one that bundles the jsdec decompiler). Defaults to the hosted CDN. const WASM_BASE_URL = (import.meta.env.VITE_WASM_BASE_URL as string | undefined)?.replace(/\/+$/, '') || 'https://indalok.github.io/rzwasi'; @@ -24,7 +24,7 @@ const FLUSH_THRESHOLD = 65536; interface WorkerScope { Module?: unknown; postMessage: (message: unknown) => void; - addEventListener: (type: 'message', listener: (event: { data: RizinInbound }) => void) => void; + addEventListener: (type: 'message', listener: (event: { data: RizinInbound; origin: string }) => void) => void; importScripts?: (...urls: string[]) => void; } @@ -130,15 +130,14 @@ function init(): void { } // Loads the Emscripten glue regardless of worker type. Classic workers (the -// production build) use importScripts; Vite's dev module workers can't, so the +// production build) use importScripts. Vite dev module workers cannot, so the // classic glue is fetched and run in global scope with importScripts shimmed so // Emscripten still detects a worker. locateFile pins the wasm to its own host. async function loadRuntime(): Promise { const url = `${WASM_BASE_URL}/rizin.js`; - // Classic workers (production build) load the glue with importScripts. Module - // workers (Vite dev) DEFINE importScripts but throw "Module scripts don't - // support importScripts()" when it's called, so typeof can't discriminate — - // try it and fall back to fetch + indirect eval on any failure. + // Module workers DEFINE importScripts but throw when it is called, so typeof + // cannot discriminate. Try it and fall back to fetch + indirect eval on any + // failure. if (typeof ctx.importScripts === 'function') { try { ctx.importScripts(url); @@ -151,7 +150,7 @@ async function loadRuntime(): Promise { if (!response.ok) throw new Error(`HTTP ${response.status} fetching rizin.js`); const source = await response.text(); // Stub importScripts so the sloppy-mode glue (which feature-detects it) finds a - // callable; locateFile already pins the wasm to its own host. + // callable. locateFile already pins the wasm to its own host. ctx.importScripts = () => {}; (0, eval)(source); } @@ -184,6 +183,18 @@ async function dispatch(active: RizinSession, request: RizinRequest): Promise { } ctx.addEventListener('message', event => { + // A dedicated worker only receives messages from the same-origin page that + // spawned it (origin is the empty string for those). Reject anything that + // arrives with a foreign origin as defense in depth. + if (event.origin && event.origin !== self.location.origin) return; const message = event.data; + if (!message || typeof message !== 'object') return; if ('control' in message) { if (message.control === 'init') init(); return; diff --git a/src/lib/rizin/rizinProtocol.ts b/src/lib/rizin/rizinProtocol.ts index 8febd8b..ba921e2 100644 --- a/src/lib/rizin/rizinProtocol.ts +++ b/src/lib/rizin/rizinProtocol.ts @@ -78,6 +78,8 @@ export interface RizinStateSnapshot { notices: RizinNotice[]; lastStderr: string; fileName: string | null; + writeMode: boolean; + isDirty: boolean; } export type RizinRequest = @@ -90,6 +92,12 @@ export type RizinRequest = | { id: number; method: 'getDecompilation'; address: number } | { id: number; method: 'exportProject' } | { id: number; method: 'importProject'; data: Uint8Array } + | { id: number; method: 'setWriteMode'; enable: boolean } + | { id: number; method: 'readFileSlice'; offset: number; length: number } + | { id: number; method: 'searchFileBytes'; needle: Uint8Array; caseInsensitive: boolean } + | { id: number; method: 'patchFile'; offset: number; hex: string } + | { id: number; method: 'exportBinary' } + | { id: number; method: 'runScript'; source: string; language: 'rz' | 'js' } | { id: number; method: 'close' }; export type RizinMethod = RizinRequest['method']; @@ -110,6 +118,12 @@ export interface RizinResultMap { analysis: AnalysisData | null; commandCatalog: Record; }; + setWriteMode: { writeMode: boolean }; + readFileSlice: { bytes: Uint8Array }; + searchFileBytes: { matches: number[] }; + patchFile: { ok: boolean; error?: string }; + exportBinary: { data: Uint8Array; name: string }; + runScript: { output: string; error?: string }; close: Record; } diff --git a/src/lib/rizin/rizinSession.ts b/src/lib/rizin/rizinSession.ts index 5e15431..19ff7f6 100644 --- a/src/lib/rizin/rizinSession.ts +++ b/src/lib/rizin/rizinSession.ts @@ -82,6 +82,9 @@ interface NativeSessionApi { getLastError: (sessionId: number) => string; autocomplete?: (sessionId: number, input: string, cursorPos: number, maxResults: number) => string; getCommandCatalog?: (sessionId: number) => string; + setWriteMode?: (sessionId: number, enable: number) => number; + commitChanges?: (sessionId: number) => number; + getFileSize?: (sessionId: number) => number; } interface CommandTokenBounds { @@ -95,8 +98,8 @@ const LARGE_BINARY_ALERT_BYTES = 1024 * 1024; const PERSIST_THROTTLE_MS = 1500; const ESC = String.fromCharCode(27); // CSI sequences (colours, cursor moves, line clears). SGR colour codes end in -// 'm' and are preserved so terminal output keeps Rizin's syntax highlighting; -// every other CSI sequence (cursor positioning, clears) is dropped. +// 'm' and are kept so terminal output retains Rizin's syntax highlighting. +// Every other CSI sequence (cursor positioning, clears) is dropped. const CSI_RE = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, 'g'); // OSC sequences (e.g. window title) terminated by BEL or String Terminator. const OSC_RE = new RegExp(`${ESC}\\][^\\u0007]*(?:\\u0007|${ESC}\\\\)`, 'g'); @@ -107,6 +110,9 @@ const NON_PRINTABLE_RE = new RegExp(`[^\\n\\r\\t${ESC} -~]`, 'g'); // Removes all ANSI styling, used where output must be plain (e.g. decompiler). const ANSI_STRIP_RE = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, 'g'); +// Matches Rizin's command-parser debug lines so we can drop them from stderr. +const PARSER_DEBUG_RE = /^\s*DEBUG:\s/; + const CACHE_BLOCKING_NOTICE_CODES = new Set([ 'output-truncated', 'metadata-unavailable', @@ -294,6 +300,10 @@ export class RizinSession { private nativeApi: NativeSessionApi | null = null; private nativeSessionId: number | null = null; private nativeApiChecked = false; + private writeMode = false; + private dirty = false; + // Mutable copy of the raw file backing the hex editor (physical offsets). + private fileImage: Uint8Array | null = null; private commandCatalogCache: Record | null = null; private persistTimer: ReturnType | null = null; private persistDirty = false; @@ -350,7 +360,13 @@ export class RizinSession { this.module._printErrHandler = (text: string) => { const cleaned = this.cleanText(text); if (!cleaned) return; - this.stderrBuffer.push(cleaned); + // Drop the parser debug spew so it neither floods the terminal nor reads as an error. + const filtered = cleaned + .split('\n') + .filter(line => !PARSER_DEBUG_RE.test(line)) + .join('\n'); + if (!filtered.trim()) return; + this.stderrBuffer.push(filtered); }; } @@ -384,6 +400,9 @@ export class RizinSession { try { const hasAutocomplete = typeof exported._rzweb_autocomplete === 'function'; const hasCommandCatalog = typeof exported._rzweb_get_command_catalog === 'function'; + const hasSetWriteMode = typeof exported._rzweb_set_write_mode === 'function'; + const hasCommitChanges = typeof exported._rzweb_commit_changes === 'function'; + const hasGetFileSize = typeof exported._rzweb_get_file_size === 'function'; return { createSession: this.module.cwrap('rzweb_create_session', 'number', []) as () => number, closeSession: this.module.cwrap('rzweb_close_session', 'number', ['number']) as (sessionId: number) => number, @@ -422,6 +441,22 @@ export class RizinSession { sessionId: number ) => string : undefined, + setWriteMode: hasSetWriteMode + ? this.module.cwrap('rzweb_set_write_mode', 'number', ['number', 'number']) as ( + sessionId: number, + enable: number + ) => number + : undefined, + commitChanges: hasCommitChanges + ? this.module.cwrap('rzweb_commit_changes', 'number', ['number']) as ( + sessionId: number + ) => number + : undefined, + getFileSize: hasGetFileSize + ? this.module.cwrap('rzweb_get_file_size', 'number', ['number']) as ( + sessionId: number + ) => number + : undefined, }; } catch { return null; @@ -600,6 +635,7 @@ export class RizinSession { } this.applyDisplayDefaults(); + this.writeMode = writeMode; return true; } @@ -607,7 +643,7 @@ export class RizinSession { // Display/runtime settings applied after any core (re)initialization. The C // session resets these to defaults on every open/load, so re-apply them here. // scr.color=3 enables truecolor SGR output so the terminal shows Rizin's - // native syntax highlighting; scr.utf8 stays off to keep output ASCII-safe. + // native syntax highlighting. scr.utf8 stays off to keep output ASCII-safe. private applyDisplayDefaults(): void { this.runNativeCommand( 'e scr.color=3;e scr.color.args=true;e scr.color.bytes=true;e scr.interactive=false;e scr.prompt=false;e scr.utf8=false;e scr.utf8.curvy=false;e log.level=0;e scr.pager=', @@ -628,7 +664,7 @@ export class RizinSession { const loaded = this.nativeApi.loadProject(this.nativeSessionId, this.projectPath, 1); if (loaded) { - // loadProject resets the core, clearing our display settings — re-apply them. + // loadProject resets the core and clears our display settings, re-apply them. this.applyDisplayDefaults(); } return !!loaded; @@ -656,8 +692,8 @@ export class RizinSession { if (!data || data.byteLength === 0) { throw new Error(this.getNativeLastError() || 'Rizin did not produce project data for this binary.'); } - // Wrap the rzdb together with the binary so the saved file reopens cold with - // a single click — a bare rzdb only stores the binary's path, not its bytes. + // Wrap the rzdb together with the binary so the saved file reopens cold. A + // bare rzdb only stores the binary's path, not its bytes. return encodeProjectBundle(this.file.name, this.file.data, data); } @@ -677,7 +713,7 @@ export class RizinSession { // Raw Rizin .rzdb: it references the binary by path, so the matching binary // must already be open for the project to resolve. if (!this._isOpen || !this.file) { - throw new Error('Open the matching binary first, then load this raw Rizin project — or load a project saved from RzWeb.'); + throw new Error('Open the matching binary first, then load this raw Rizin project, or load a project saved from RzWeb.'); } if (!this.restoreNativeProject(projectData)) { @@ -705,6 +741,189 @@ export class RizinSession { this.emitAnalysisChanged(); } + // Reopen the file read-write or read-only without losing analysis. + setWriteMode(enable: boolean): boolean { + if (!this._isOpen || !this.file) { + throw new Error('Open a binary before toggling write mode.'); + } + if (!this.hasNativeSession() || !this.nativeApi || this.nativeSessionId == null) { + throw new Error('Write mode needs the native session API, which is unavailable in this build.'); + } + + if (this.nativeApi.setWriteMode) { + this.nativeApi.setWriteMode(this.nativeSessionId, enable ? 1 : 0); + } else { + // oo+ is read-write, oo is read-only. + this.runNativeCommand(enable ? 'oo+' : 'oo', { + context: 'metadata', + commandLabel: 'Toggle write mode', + suppressNotice: true, + }); + } + + // Reopening resets the core display settings, so restore them. + this.applyDisplayDefaults(); + this.writeMode = enable; + this.refreshCurrentAddress(); + return this.writeMode; + } + + // --- Raw file access (hex editor) ----------------------------------------- + // The hex view works on the raw file image at physical offsets, so search and + // edits are like HxD (hehe) (every byte, mapped or not). + + readFileSlice(offset: number, length: number): Uint8Array { + if (!this.fileImage || offset < 0 || offset >= this.fileImage.length) { + return new Uint8Array(0); + } + return this.fileImage.slice(offset, Math.min(offset + length, this.fileImage.length)); + } + + // Exact byte search over the whole raw file. caseInsensitive folds ASCII A-Z. + searchFileBytes(needle: Uint8Array, caseInsensitive: boolean): number[] { + const hay = this.fileImage; + const n = needle.length; + if (!hay || n === 0 || n > hay.length) return []; + const fold = caseInsensitive + ? (b: number) => (b >= 65 && b <= 90 ? b + 32 : b) + : (b: number) => b; + const first = fold(needle[0]); + const matches: number[] = []; + const limit = hay.length - n; + for (let i = 0; i <= limit; i++) { + if (fold(hay[i]) !== first) continue; + let ok = true; + for (let j = 1; j < n; j++) { + if (fold(hay[i + j]) !== fold(needle[j])) { + ok = false; + break; + } + } + if (ok) { + matches.push(i); + if (matches.length >= 100000) break; + } + } + return matches; + } + + patchFile(offset: number, hex: string): { ok: boolean; error?: string } { + const clean = hex.replace(/[^0-9a-fA-F]/g, ''); + if (clean.length === 0 || clean.length % 2 !== 0) { + return { ok: false, error: 'Enter an even number of hex digits.' }; + } + if (!this.fileImage) return { ok: false, error: 'No binary is open.' }; + const bytes = new Uint8Array(clean.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); + } + if (offset < 0 || offset + bytes.length > this.fileImage.length) { + return { ok: false, error: 'Patch is out of range.' }; + } + this.fileImage.set(bytes, offset); + this.dirty = true; + return { ok: true }; + } + + // Returns the edited raw file. Merges in any terminal write-cache edits. + async exportBinary(): Promise<{ data: Uint8Array; name: string }> { + if (!this.file || !this.fileImage) { + throw new Error('Open a binary before exporting it.'); + } + const merged = new Uint8Array(this.fileImage); + if (this.hasNativeSession() && this.nativeApi && this.nativeSessionId != null) { + if (this.nativeApi.commitChanges) this.nativeApi.commitChanges(this.nativeSessionId); + else this.runNativeCommand('wci', { context: 'metadata', commandLabel: 'Commit changes', suppressNotice: true }); + const rizinBytes = this.filePath ? readFsBytes(this.module, this.filePath) : null; + const orig = this.file.data; + if (rizinBytes && rizinBytes.length === orig.length) { + // Apply terminal/rizin edits where the hex editor did not change a byte. + for (let i = 0; i < merged.length; i++) { + if (rizinBytes[i] !== orig[i] && merged[i] === orig[i]) merged[i] = rizinBytes[i]; + } + } + } + this.dirty = false; + return { data: merged, name: this.file.name }; + } + + // --- Scripting ------------------------------------------------------------ + + // Synchronous command API exposed to JavaScript scripts + private buildScriptApi(logs: string[]) { + const runCmd = (command: unknown): string => { + const result = this.runNativeCommand(String(command ?? ''), { + context: 'command', + commandLabel: 'Script', + suppressNotice: true, + }); + return this.stripAnsi(result.output); + }; + const runCmdJson = (command: unknown): unknown => { + const out = runCmd(command).trim(); + return out ? JSON.parse(out) : null; + }; + const push = (args: unknown[]) => + logs.push(args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')); + return { + cmd: runCmd, + call: runCmd, + cmdj: runCmdJson, + callj: runCmdJson, + cmdAt: (command: unknown, at: unknown) => runCmd(`${command} @ ${at}`), + log: (...args: unknown[]) => push(args), + }; + } + + runScript(source: string, language: 'rz' | 'js'): { output: string; error?: string } { + if (!this._isOpen || !this.file) { + return { output: '', error: 'Open a binary before running a script.' }; + } + if (!this.hasNativeSession()) { + return { output: '', error: 'Scripting needs the native session API.' }; + } + + if (language === 'rz') { + const path = `${this.workDir}/.rzweb-script.rz`; + try { + this.module.FS.writeFile(path, source); + } catch { + return { output: '', error: 'Could not stage the script.' }; + } + const result = this.runNativeCommand(`. ${path}`, { + context: 'command', + commandLabel: 'Run script', + suppressNotice: true, + }); + try { + this.module.FS.unlink(path); + } catch { + // Already gone, ignore. + } + const stderr = this.stderrBuffer.join('\n').trim(); + return { output: this.stripAnsi(result.output), error: stderr || undefined }; + } + + const logs: string[] = []; + const api = this.buildScriptApi(logs); + const consoleShim = { log: api.log, info: api.log, warn: api.log, error: api.log }; + try { + // The Scripts panel is an opt-in sandbox: the user runs their own + // JavaScript against the rz API inside this worker, like a REPL. The + // source is authored locally by the same operator (typed in the editor or + // loaded from a file they pick), never supplied by a remote party, so this + // is not a code-injection vector despite the editor-to-Function dataflow. + const fn = new Function('rz', 'console', `"use strict";\n${source}`); + const returned = fn(api, consoleShim); + if (returned !== undefined) { + logs.push(typeof returned === 'string' ? returned : JSON.stringify(returned, null, 2)); + } + return { output: logs.join('\n') }; + } catch (error) { + return { output: logs.join('\n'), error: error instanceof Error ? error.message : String(error) }; + } + } + get isOpen(): boolean { return this._isOpen; } @@ -791,6 +1010,8 @@ export class RizinSession { notices: [...this.notices], lastStderr: this.lastCommandStderr, fileName: this.file?.name ?? null, + writeMode: this.writeMode, + isDirty: this.dirty, }; } @@ -1045,7 +1266,8 @@ export class RizinSession { try { this.module.callMain(args); } catch { - // Rizin/Emscripten may throw after command completion; the captured buffers are the source of truth. + // Rizin/Emscripten may throw after a command completes. The captured + // buffers are the source of truth. } finally { const capture = this.activeOutputCapture; this.activeOutputCapture = null; @@ -1727,6 +1949,9 @@ export class RizinSession { this.currentAddress = '0x00000000'; this.notices = []; this.commandCatalogCache = null; + this.writeMode = false; + this.dirty = false; + this.fileImage = new Uint8Array(file.data); this.runtimeConfig = { ioCache: config?.ioCache ?? true, analysisDepth: config?.analysisDepth ?? 2, @@ -1763,7 +1988,7 @@ export class RizinSession { } // An explicit project payload (cold-loading a saved RzWeb project) takes - // precedence over any cached analysis; otherwise fall back to cached project + // precedence over any cached analysis. Otherwise fall back to cached project // data so reopening a previously-analyzed binary restores instantly. const explicitRestore = !!restoreProjectData && restoreProjectData.byteLength > 0; const projectToRestore = explicitRestore ? restoreProjectData : cached?.projectData; @@ -1802,7 +2027,8 @@ export class RizinSession { return; } - const nativeSessionReady = this.startNativeFileSession(); + // Open r/w so hex and terminal edits persist and can be exported. + const nativeSessionReady = this.startNativeFileSession(true); this.refreshCurrentAddress(); if (file.data.length >= LARGE_BINARY_ALERT_BYTES) { @@ -1902,11 +2128,15 @@ export class RizinSession { commandLabel: 'Command output', }); - // Capture stderr now — before the background refresh below runs more - // commands — so the terminal surfaces only the user command's own errors. + // Capture stderr now, before the background refresh below runs more + // commands, so the terminal surfaces only the user command's own errors. this.lastCommandStderr = this.stderrBuffer.join('\n'); this.updateCurrentAddressFromCommand(finalCmd); + // Terminal write commands also dirty the binary. + if (this.writeMode && /(^|;)\s*w/i.test(originalCmd)) { + this.dirty = true; + } item.resolve(result.output); const refreshPlan = this.buildRefreshPlan(originalCmd); @@ -1954,7 +2184,7 @@ export class RizinSession { return this.currentAddress; } - // Reads `size` bytes at `address` via a temporary `@` seek; short reads at an + // Reads `size` bytes at `address` via a temporary `@` seek. Short reads at an // unmapped tail return fewer bytes, which callers pad with placeholders. readMemory(address: number, size: number): Uint8Array { const safeSize = Math.max(0, Math.min(Math.floor(size), 1 << 20)); @@ -2028,9 +2258,9 @@ export class RizinSession { } // Renders the function at `address`. Uses a real decompiler plugin (pdg from - // rz-ghidra, pdd from jsdec, or pdc) when the build ships one; otherwise falls - // back to Rizin's pseudo-disassembly so the view is never empty. The `pseudo` - // flag lets the UI label fallback output honestly. + // rz-ghidra, pdd from jsdec, or pdc) when the build ships one, else falls back + // to Rizin's pseudo-disassembly so the view is never empty. The `pseudo` flag + // lets the UI label fallback output. getDecompilation(address: number): { code: string; pseudo: boolean } { if (!this._isOpen) return { code: '', pseudo: false }; const addr = `0x${Math.max(0, Math.floor(address)).toString(16)}`; @@ -2097,6 +2327,9 @@ export class RizinSession { } this._isOpen = false; + this.writeMode = false; + this.dirty = false; + this.fileImage = null; this.stdoutBuffer = []; this.stderrBuffer = []; this.lastCommandStderr = ''; diff --git a/src/lib/shortcuts.ts b/src/lib/shortcuts.ts index 1f0ea1f..e6abf54 100644 --- a/src/lib/shortcuts.ts +++ b/src/lib/shortcuts.ts @@ -30,6 +30,9 @@ export const VIEW_SHORTCUTS: readonly ViewShortcut[] = [ export const EXTRA_VIEWS: readonly ViewShortcut[] = [ { view: 'decompiler', label: 'Decompiler' }, { view: 'xrefs', label: 'Xrefs' }, + { view: 'scripts', label: 'Scripts' }, + { view: 'flags', label: 'Flags' }, + { view: 'callgraph', label: 'Call Graph' }, ]; export interface KeyShortcut { diff --git a/src/lib/themes.ts b/src/lib/themes.ts new file mode 100644 index 0000000..3db3f2b --- /dev/null +++ b/src/lib/themes.ts @@ -0,0 +1,41 @@ +export type ThemeId = + | 'rizin-dark' + | 'midnight' + | 'nord' + | 'dracula' + | 'carbon' + | 'rizin-light' + | 'solarized-light'; + +export type Appearance = 'light' | 'dark'; + +export interface ThemeMeta { + id: ThemeId; + name: string; + appearance: Appearance; + // Hex preview swatches for the theme picker. + swatch: { bg: string; surface: string; primary: string; accent: string }; +} + +export const THEMES: ThemeMeta[] = [ + { id: 'rizin-dark', name: 'Rizin Dark', appearance: 'dark', swatch: { bg: '#0f1521', surface: '#161c28', primary: '#0fa6e9', accent: '#a78bfa' } }, + { id: 'midnight', name: 'Midnight', appearance: 'dark', swatch: { bg: '#0b0f1f', surface: '#121830', primary: '#22d3ee', accent: '#818cf8' } }, + { id: 'nord', name: 'Nord', appearance: 'dark', swatch: { bg: '#2e3440', surface: '#3b4252', primary: '#88c0d0', accent: '#81a1c1' } }, + { id: 'dracula', name: 'Dracula', appearance: 'dark', swatch: { bg: '#282a36', surface: '#343746', primary: '#bd93f9', accent: '#ff79c6' } }, + { id: 'carbon', name: 'Carbon', appearance: 'dark', swatch: { bg: '#121212', surface: '#1a1a1a', primary: '#f5a524', accent: '#f59e0b' } }, + { id: 'rizin-light', name: 'Rizin Light', appearance: 'light', swatch: { bg: '#ffffff', surface: '#f4f7fb', primary: '#0d8ecf', accent: '#7c3aed' } }, + { id: 'solarized-light', name: 'Solarized Light', appearance: 'light', swatch: { bg: '#fdf6e3', surface: '#eee8d5', primary: '#268bd2', accent: '#2aa198' } }, +]; + +export const DEFAULT_DARK_THEME: ThemeId = 'rizin-dark'; +export const DEFAULT_LIGHT_THEME: ThemeId = 'rizin-light'; + +const THEME_MAP = new Map(THEMES.map((t) => [t.id, t])); + +export function isThemeId(value: string): value is ThemeId { + return THEME_MAP.has(value as ThemeId); +} + +export function getThemeMeta(id: ThemeId): ThemeMeta { + return THEME_MAP.get(id) ?? THEME_MAP.get(DEFAULT_DARK_THEME)!; +} diff --git a/src/lib/utils/colors.ts b/src/lib/utils/colors.ts new file mode 100644 index 0000000..9b0a232 --- /dev/null +++ b/src/lib/utils/colors.ts @@ -0,0 +1,25 @@ +// Converts an "H S% L%" CSS variable triple into a hex color. +export function hslTripleToHex(triple: string): string { + const match = triple.trim().match(/^([\d.]+)\s+([\d.]+)%\s+([\d.]+)%$/); + if (!match) return '#000000'; + const h = parseFloat(match[1]); + const s = parseFloat(match[2]) / 100; + const l = parseFloat(match[3]) / 100; + const c = (1 - Math.abs(2 * l - 1)) * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m = l - c / 2; + let r = 0, g = 0, b = 0; + if (h < 60) { r = c; g = x; } + else if (h < 120) { r = x; g = c; } + else if (h < 180) { g = c; b = x; } + else if (h < 240) { g = x; b = c; } + else if (h < 300) { r = x; b = c; } + else { r = c; b = x; } + const hex = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0'); + return `#${hex(r)}${hex(g)}${hex(b)}`; +} + +// Reads a themed CSS variable (e.g. --primary) as a hex color. +export function cssVarHex(name: string): string { + return hslTripleToHex(getComputedStyle(document.documentElement).getPropertyValue(name)); +} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index c62a662..09e3780 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,2 +1,3 @@ export { cn } from './cn'; export * from './format'; +export * from './colors'; diff --git a/src/lib/utils/version.ts b/src/lib/utils/version.ts index 13044f5..dc5d969 100644 --- a/src/lib/utils/version.ts +++ b/src/lib/utils/version.ts @@ -24,9 +24,9 @@ async function fetchVersion(): Promise { } } } catch { - + // Local /VERSION missing, fall through to the remote source. } - + try { const base = (import.meta as unknown as { env?: { VITE_WASM_BASE_URL?: string } }).env?.VITE_WASM_BASE_URL?.replace(/\/+$/, '') || @@ -39,9 +39,9 @@ async function fetchVersion(): Promise { } } } catch { - + // Remote VERSION unreachable (offline build), report unknown rather than throw. } - + return 'unknown'; } diff --git a/src/main.tsx b/src/main.tsx index 74da4a2..f9aed2c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -37,7 +37,7 @@ if (!container) throw new Error('Root element not found'); createRoot(container).render( - + diff --git a/src/pages/AnalysisPage.tsx b/src/pages/AnalysisPage.tsx index bcc6957..a651c44 100644 --- a/src/pages/AnalysisPage.tsx +++ b/src/pages/AnalysisPage.tsx @@ -1,16 +1,19 @@ -import { useEffect, useRef, useState, useCallback, useMemo, type ChangeEvent } from 'react'; +import { useEffect, useRef, useState, useCallback, useMemo, lazy, Suspense, type ChangeEvent } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import { Panel, Group, Separator } from 'react-resizable-panels'; -import { useFileStore, useRizinStore, useUIStore, useSettingsStore, type ActivePanel } from '@/stores'; +import { useFileStore, useRizinStore, useUIStore, useSettingsStore, useTabStore, type ActivePanel } from '@/stores'; import { loadRizinModule, getCachedVersions, RizinInstance, decodeProjectBundle, type RizinNotice } from '@/lib/rizin'; import { useKeyboardShortcuts } from '@/hooks'; import { RizinTerminal } from '@/components/terminal'; -import { HexView, FunctionsView, StringsView, GraphView, DisassemblyView, ImportsView, ExportsView, SectionsView, HeaderInfoPanel, XrefsView, DecompilerView } from '@/components/views'; +import { HexView, FunctionsView, StringsView, GraphView, DisassemblyView, ImportsView, ExportsView, SectionsView, HeaderInfoPanel, XrefsView, DecompilerView, FlagsView, CallGraphView } from '@/components/views'; + +// Code-split the script editor so CodeMirror only loads when Scripts is opened. +const ScriptsView = lazy(() => import('@/components/views/ScriptsView').then((m) => ({ default: m.ScriptsView }))); import { Button, Progress, Badge, Tabs, TabsList, TabsTrigger, CommandPalette, SettingsDialog, ShortcutsDialog } from '@/components/ui'; import { cn, stripAnsi } from '@/lib/utils'; -import { Menu, X, Terminal as TerminalIcon, Settings, Code, Layout, Share2, Quote, FileCode, Home, Package, ArrowUpRight, Layers, Info, AlertTriangle, Save, FolderOpen, Braces, ArrowLeftRight } from 'lucide-react'; +import { Menu, X, Terminal as TerminalIcon, Settings, Code, Layout, Share2, Quote, FileCode, Home, Package, ArrowUpRight, Layers, Info, AlertTriangle, Save, FolderOpen, Braces, ArrowLeftRight, Pencil, Download, ScrollText, Plus, Flag, Network } from 'lucide-react'; import type { RzFunction, RzDisasmLine, RzString, RzImport, RzExport, RzSection } from '@/types/rizin'; function useResponsiveLayout() { @@ -36,16 +39,14 @@ function useResponsiveLayout() { return layout; } -// Pull the binary's base addr out of whichever info payload carries it. -function readBaddr(info: unknown): number | null { - if (!info || typeof info !== 'object') return null; - const record = info as Record; - const binaryInfo = record.binaryInfo as Record | undefined; - if (binaryInfo && typeof binaryInfo.baddr === 'number') return binaryInfo.baddr; - const overview = record.overview as Record | undefined; - const bin = overview?.bin as Record | undefined; - if (bin && typeof bin.baddr === 'number') return bin.baddr; - if (typeof record.baddr === 'number') return record.baddr; +// Maps a virtual addr to its physical file offset via the section table. +function vaddrToOffset(vaddr: number, sections: RzSection[]): number | null { + for (const s of sections) { + const size = s.size || s.vsize || 0; + if (typeof s.vaddr === 'number' && size > 0 && vaddr >= s.vaddr && vaddr < s.vaddr + size) { + return (s.paddr ?? 0) + (vaddr - s.vaddr); + } + } return null; } @@ -107,12 +108,14 @@ function parseAddress(value: unknown): number | undefined { export default function AnalysisPage() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); - const version = searchParams.get('version') || 'latest'; const shouldCache = searchParams.get('cache') === 'true'; - const { currentFile, clearCurrentFile, setCurrentFile } = useFileStore(); + const { clearCurrentFile, currentFile: launchedFile } = useFileStore(); + const { tabs, activeTabId, openTab, closeTab, setActiveTab, parkTab, reset: resetTabs } = useTabStore(); + const activeTab = tabs.find((t) => t.id === activeTabId) ?? null; + const currentFile = activeTab?.file ?? null; const { isLoading, setLoading, loadProgress, setLoadProgress, loadPhase, setLoadPhase, setLoadMessage, setCachedVersions, setError } = useRizinStore(); - const { sidebarOpen, setSidebarOpen, splitDirection, setSettingsDialogOpen, currentAddress, setCurrentAddress, currentView, setCurrentView, selectedFunction, setSelectedFunction } = useUIStore(); + const { sidebarOpen, setSidebarOpen, splitDirection, setSettingsDialogOpen, currentAddress, setCurrentAddress, currentView, setCurrentView, selectedFunction, setSelectedFunction, setHexTarget } = useUIStore(); const { ioCache, analysisDepth, noAnalysis, maxOutputSizeMb } = useSettingsStore(); const { narrow, portrait } = useResponsiveLayout(); const stacked = narrow || portrait; @@ -130,12 +133,41 @@ export default function AnalysisPage() { const [graphEdges, setGraphEdges] = useState([]); const [pendingCommand, setPendingCommand] = useState(null); const [projectAction, setProjectAction] = useState<'save' | 'load' | null>(null); + const [writeMode, setWriteMode] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const [togglingWriteMode, setTogglingWriteMode] = useState(false); + const [exportingBinary, setExportingBinary] = useState(false); const functionDetailRequestRef = useRef(0); const projectInputRef = useRef(null); + const addInputRef = useRef(null); + // Per-tab Rizin instances persist here so background analysis keeps running + // while another tab is active. Disposed on tab close and on leaving Analysis. + const instancesRef = useRef(new Map }>()); - const functions = !activeInstance?.analysis || !analysisReady - ? [] - : activeInstance.analysis.functions as RzFunction[]; + // Seed the first tab from the binary launched on the Home page. + useEffect(() => { + if (tabs.length === 0) { + if (launchedFile) openTab(launchedFile); + else navigate('/'); + } + // Run once on mount. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Dispose every tab's worker when leaving the Analysis page. + useEffect(() => { + const instances = instancesRef.current; + return () => { + for (const entry of instances.values()) void entry.instance.close(); + instances.clear(); + }; + }, []); + + const functions = useMemo(() => { + void analysisRevision; + if (!activeInstance?.analysis || !analysisReady) return []; + return activeInstance.analysis.functions as RzFunction[]; + }, [activeInstance, analysisReady, analysisRevision]); const strings = !activeInstance?.analysis || !analysisReady ? [] @@ -159,23 +191,14 @@ export default function AnalysisPage() { ? null : activeInstance.analysis.info; - // Hex view span - const hexLayout = useMemo(() => { - let min = Infinity; - let max = 0; - for (const section of sections) { - if (typeof section.vaddr === 'number' && typeof section.vsize === 'number' && section.vaddr > 0 && section.vsize > 0) { - min = Math.min(min, section.vaddr); - max = Math.max(max, section.vaddr + section.vsize); - } - } - if (min !== Infinity && max > min) { - return { base: min, size: max - min }; - } - return { base: readBaddr(infoPayload) ?? 0, size: currentFile?.size ?? 0 }; - }, [sections, infoPayload, currentFile?.size]); + // The hex view shows the raw file at physical offsets (like a hex editor). + const hexLayout = useMemo(() => ({ base: 0, size: currentFile?.size ?? 0 }), [currentFile?.size]); useEffect(() => { + // Reset edit flags. The open response reports the real write-mode. + setWriteMode(false); + setIsDirty(false); + if (!activeInstance) { setAlerts([]); return; @@ -197,88 +220,118 @@ export default function AnalysisPage() { }); }); + // Sync write-mode and dirty flags after any edit, including terminal writes. + const unsubscribeState = activeInstance.onStateChanged(() => { + setWriteMode(activeInstance.isWriteMode); + setIsDirty(activeInstance.isDirty); + }); + return () => { unsubscribeAnalysis(); unsubscribeNotice(); + unsubscribeState(); }; }, [activeInstance]); useEffect(() => { - if (!currentFile) { - navigate('/'); - return; - } - - let rz: RizinInstance | null = null; + if (!currentFile) return; + const tabId = currentFile.id; + const file = currentFile; + let cancelled = false; - const initRizin = async () => { - setLoading(true); - setLoadPhase('initializing'); - setLoadProgress(0); + const attach = async () => { + setAnalysisReady(false); setAlerts([]); setAnalysisRevision(0); - setAnalysisReady(false); setDisasmLines([]); setGraphNodes([]); setGraphEdges([]); - setSelectedFunction(null); - setCurrentAddress(0); - try { - const worker = await loadRizinModule({ - onProgress: ({ phase, progress, message }) => { - setLoadPhase(phase); - setLoadProgress(progress); - setLoadMessage(message); - }, - }); + let entry = instancesRef.current.get(tabId); + const needsLoad = !entry || !entry.ready; + if (needsLoad && !cancelled) { + setLoading(true); + setLoadPhase(entry ? 'analyzing' : 'initializing'); + setLoadProgress(entry ? 78 : 0); + } - const versions = await getCachedVersions(); - setCachedVersions(versions); - - rz = new RizinInstance(worker); - setLoadPhase('analyzing'); - setLoadProgress(78); - setLoadMessage(noAnalysis ? 'Opening binary without auto-analysis...' : 'Running initial analysis and indexing binary data...'); - await rz.open(currentFile, { - ioCache, - analysisDepth, - noAnalysis, - maxOutputBytes: maxOutputSizeMb * 1024 * 1024, - enableCache: shouldCache, - extraArgs: ['-e', 'scr.color=0', '-e', 'scr.utf8=false'], - }, currentFile.projectData); - - setActiveInstance(rz); - const initialSeek = Number.parseInt(rz.getCurrentAddress(), 16); - if (!Number.isNaN(initialSeek)) { - setCurrentAddress(initialSeek); - } - setAnalysisReady(true); - setAlerts(rz.allNotices); - setLoadPhase('ready'); - if (rz.cacheHit) { - toast.success('Loaded from analysis cache'); - } else { - toast.success(noAnalysis ? 'Binary opened' : 'Analysis complete'); + try { + if (!entry) { + const worker = await loadRizinModule({ + onProgress: ({ phase, progress, message }) => { + if (cancelled) return; + setLoadPhase(phase); + setLoadProgress(progress); + setLoadMessage(message); + }, + }); + const versions = await getCachedVersions(); + setCachedVersions(versions); + const rz = new RizinInstance(worker); + if (!cancelled) { + setLoadPhase('analyzing'); + setLoadProgress(78); + setLoadMessage(noAnalysis ? 'Opening binary without auto-analysis...' : 'Running initial analysis and indexing binary data...'); + } + const promise = rz.open(file, { + ioCache, + analysisDepth, + noAnalysis, + maxOutputBytes: maxOutputSizeMb * 1024 * 1024, + enableCache: shouldCache, + extraArgs: ['-e', 'scr.color=0', '-e', 'scr.utf8=false'], + }, file.projectData); + entry = { instance: rz, ready: false, promise }; + instancesRef.current.set(tabId, entry); + await promise; + entry.ready = true; + if (!cancelled) { + toast.success(rz.cacheHit ? 'Loaded from analysis cache' : noAnalysis ? 'Binary opened' : 'Analysis complete'); + } + } else if (!entry.ready) { + await entry.promise; + entry.ready = true; } - } catch (error) { + instancesRef.current.delete(tabId); console.error('Failed to load Rizin:', error); - setError(String(error)); - setLoadPhase('error'); - toast.error(`Failed to load Rizin: ${error}`); - } finally { - setLoading(false); + if (!cancelled) { + setError(String(error)); + setLoadPhase('error'); + setLoading(false); + toast.error(`Failed to load Rizin: ${error}`); + } + return; } - }; - initRizin(); + if (cancelled) return; + const rz = entry.instance; + setLoading(false); + setLoadPhase('ready'); + setActiveInstance(rz); + setAnalysisReady(true); + setAlerts(rz.allNotices); + + // Restore this tab's parked view, or seek to the binary's entrypoint. + const parked = useTabStore.getState().tabs.find((t) => t.id === tabId)?.parked; + if (parked) { + setCurrentView(parked.view); + setSelectedFunction(parked.selectedFunction); + setCurrentAddress(parked.address); + } else { + setSelectedFunction(null); + const initialSeek = Number.parseInt(rz.getCurrentAddress(), 16); + setCurrentAddress(Number.isNaN(initialSeek) ? 0 : initialSeek); + } + }; + void attach(); return () => { - void rz?.close(); + cancelled = true; }; - }, [version, shouldCache, currentFile, navigate, setLoading, setLoadPhase, setLoadProgress, setLoadMessage, setCachedVersions, setError, ioCache, analysisDepth, noAnalysis, maxOutputSizeMb, setCurrentAddress, setSelectedFunction]); + // Re-attach only when the active tab changes. Config is read at attach time. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentFile?.id]); const buildDisassemblyLines = useCallback((disasm: unknown): RzDisasmLine[] => { const parsed = disasm as RawDisasm | RawDisasmOp[] | null; @@ -294,9 +347,8 @@ export default function AnalysisPage() { offset: op.offset ?? 0, size: op.size ?? 0, bytes: op.bytes ?? '', - // `disasm` resolves flags/symbols (e.g. `call sym.imp.printf`) where - // `opcode` keeps raw addr; prefer the former. Strip ANSI in case - // scr.color leaked escape codes into the JSON field. + // Prefer `disasm` (flags/symbols resolved) over raw `opcode`, and strip + // ANSI in case scr.color leaked escapes into the JSON. opcode: stripAnsi(op.opcode ?? op.disasm ?? ''), disasm: stripAnsi(op.disasm ?? op.opcode ?? ''), family: op.family ?? '', @@ -420,6 +472,43 @@ export default function AnalysisPage() { if (view) setCurrentView(view); }, [setCurrentAddress, setCurrentView]); + const handleShowInHex = useCallback((vaddr: number) => { + const offset = vaddrToOffset(vaddr, sections); + if (offset == null) { + toast.error('That address is not in a file-backed section.'); + return; + } + setHexTarget(offset); + setCurrentView('hex'); + }, [sections, setHexTarget, setCurrentView]); + + const handleRenameFunction = useCallback(async (offset: number, name: string) => { + if (!activeInstance) return; + const safe = name.trim().replace(/[^A-Za-z0-9_.]/g, '_'); + if (!safe) return; + await activeInstance.executeCommand(`afn ${safe} @ 0x${offset.toString(16)}`); + setAnalysisRevision((v) => v + 1); + if (selectedFunction && currentAddress > 0) void loadFunctionPresentation(currentAddress); + toast.success('Function renamed'); + }, [activeInstance, selectedFunction, currentAddress, loadFunctionPresentation]); + + const handleSetComment = useCallback(async (addr: number, text: string) => { + if (!activeInstance) return; + const comment = text.trim().replace(/[\n\r@]/g, ' '); + await activeInstance.executeCommand(comment ? `CC ${comment} @ 0x${addr.toString(16)}` : `CC- @ 0x${addr.toString(16)}`); + if (currentAddress > 0) void loadFunctionPresentation(currentAddress); + toast.success(comment ? 'Comment set' : 'Comment removed'); + }, [activeInstance, currentAddress, loadFunctionPresentation]); + + // Seek to a function by address (call-graph node click) and show its disasm. + const handleFunctionSelectByAddress = useCallback((addr: number) => { + setCurrentAddress(addr); + const fn = functions.find((f) => f.offset === addr); + if (fn) setSelectedFunction(fn.name); + setCurrentView('disasm'); + void loadFunctionPresentation(addr); + }, [functions, setCurrentAddress, setSelectedFunction, setCurrentView, loadFunctionPresentation]); + const handleRunCommand = useCallback((command: string) => { setPendingCommand(command); setCurrentView('terminal'); @@ -427,6 +516,13 @@ export default function AnalysisPage() { const clearPendingCommand = useCallback(() => setPendingCommand(null), []); + // Save the active tab's view state so it can be restored when switched back. + const parkActiveTab = useCallback(() => { + if (activeTabId) { + parkTab(activeTabId, { view: currentView, address: currentAddress, selectedFunction }); + } + }, [activeTabId, parkTab, currentView, currentAddress, selectedFunction]); + const handleSaveProject = useCallback(async () => { if (!activeInstance || !currentFile) return; @@ -453,6 +549,49 @@ export default function AnalysisPage() { } }, [activeInstance, currentFile]); + const handleToggleWriteMode = useCallback(async () => { + if (!activeInstance) return; + setTogglingWriteMode(true); + try { + const next = await activeInstance.setWriteMode(!writeMode); + setWriteMode(next); + toast.success(next ? 'Editing unlocked' : 'Editing locked'); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Could not toggle editing'); + } finally { + setTogglingWriteMode(false); + } + }, [activeInstance, writeMode]); + + const handleExportBinary = useCallback(async () => { + if (!activeInstance) return; + setExportingBinary(true); + try { + const { data, name } = await activeInstance.exportBinary(); + const buffer = new ArrayBuffer(data.byteLength); + new Uint8Array(buffer).set(data); + const blob = new Blob([buffer], { type: 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + const safeName = (name || 'binary').replace(/[\\/:*?"<>|]+/g, '_'); + // foo.exe becomes foo.patched.exe so the extension stays valid. + const dot = safeName.lastIndexOf('.'); + const downloadName = dot > 0 ? `${safeName.slice(0, dot)}.patched${safeName.slice(dot)}` : `${safeName}.patched`; + link.href = url; + link.download = downloadName; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + setIsDirty(false); + toast.success('Binary saved'); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Could not save binary'); + } finally { + setExportingBinary(false); + } + }, [activeInstance]); + const handleLoadProjectClick = useCallback(() => { projectInputRef.current?.click(); }, []); @@ -471,7 +610,8 @@ export default function AnalysisPage() { // sync, works even when the open binary differs from the project's). const bundle = decodeProjectBundle(bytes); if (bundle) { - setCurrentFile({ + parkActiveTab(); + openTab({ id: crypto.randomUUID(), name: bundle.name, data: bundle.binary, @@ -506,7 +646,7 @@ export default function AnalysisPage() { } finally { setProjectAction(null); } - }, [activeInstance, setCurrentAddress, setSelectedFunction, setCurrentFile]); + }, [activeInstance, setCurrentAddress, setSelectedFunction, setAnalysisReady, parkActiveTab, openTab]); useEffect(() => { if (!activeInstance || !selectedFunction || currentAddress <= 0) { @@ -531,11 +671,58 @@ export default function AnalysisPage() { selectedFunction, ]); + const handleSwitchTab = useCallback((id: string) => { + if (id === activeTabId) return; + parkActiveTab(); + setActiveTab(id); + }, [activeTabId, parkActiveTab, setActiveTab]); + + const handleCloseTab = useCallback((id: string) => { + const entry = instancesRef.current.get(id); + if (entry) { + void entry.instance.close(); + instancesRef.current.delete(id); + } + const wasLast = tabs.length <= 1; + closeTab(id); + if (wasLast) { + clearCurrentFile(); + navigate('/'); + } + }, [tabs.length, closeTab, clearCurrentFile, navigate]); + + // Each tab holds a full WASM worker, so cap the count (tighter on mobile). + const maxTabs = stacked ? 3 : 8; + const atTabLimit = tabs.length >= maxTabs; + + const handleAddBinary = useCallback(() => { + if (tabs.length >= maxTabs) { + toast.error(`Up to ${maxTabs} binaries can be open at once.`); + return; + } + addInputRef.current?.click(); + }, [tabs.length, maxTabs]); + + const handleAddFileSelected = useCallback(async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + event.target.value = ''; + if (!file) return; + try { + const data = new Uint8Array(await file.arrayBuffer()); + parkActiveTab(); + openTab({ id: crypto.randomUUID(), name: file.name, data, size: file.size, loadedAt: Date.now() }); + } catch { + toast.error('Unable to open the selected binary.'); + } + }, [parkActiveTab, openTab]); + const handleGoHome = useCallback(() => { - void activeInstance?.close(); + for (const entry of instancesRef.current.values()) void entry.instance.close(); + instancesRef.current.clear(); + resetTabs(); clearCurrentFile(); navigate('/'); - }, [activeInstance, clearCurrentFile, navigate]); + }, [resetTabs, clearCurrentFile, navigate]); if (isLoading) { return ( @@ -559,6 +746,40 @@ export default function AnalysisPage() { return (
+
+ {tabs.map((tab) => ( +
handleSwitchTab(tab.id)} + title={tab.file.name} + className={cn( + 'group flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-t border-b-2 px-2.5 text-xs transition-colors', + tab.id === activeTabId + ? 'border-primary bg-background text-foreground' + : 'border-transparent text-muted-foreground hover:bg-accent/40' + )} + > + + {tab.file.name} + +
+ ))} + + +
+ @@ -644,6 +888,15 @@ export default function AnalysisPage() { Xrefs + + Scripts + + + Flags + + + Call Graph + Imports @@ -691,22 +944,25 @@ export default function AnalysisPage() { )}
- + {sidebarOpen && ( <> - )} - - + +
{currentView === 'terminal' && activeInstance && ( ) : disasmLines.length > 0 ? ( - + ) : (
@@ -742,17 +998,24 @@ export default function AnalysisPage() {
)} {currentView === 'decompiler' && activeInstance && } - {currentView === 'hex' && activeInstance && } + {currentView === 'hex' && activeInstance && setIsDirty(true)} />} {currentView === 'strings' && setCurrentAddress(s.vaddr)} />} {currentView === 'graph' && } {currentView === 'xrefs' && activeInstance && } + {currentView === 'flags' && activeInstance && } + {currentView === 'callgraph' && activeInstance && } + {currentView === 'scripts' && activeInstance && ( + Loading editor...
}> + + + )} {currentView === 'imports' && } {currentView === 'exports' && } {currentView === 'sections' && } {currentView === 'info' && }
- +