diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index a594bb5f57..9031969b8e 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,6 +1,5 @@ description: Create a report to help us improve name: Bug report -title: "bug: " type: Bug labels: ["needs triage"] diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index fdff17696a..17f451ae3f 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,7 +1,6 @@ description: Suggest an idea for this project. labels: ["needs triage"] name: "Feature request" -title: "feature: " type: Enhancement body: diff --git a/.github/workflows/tests-backend.yml b/.github/workflows/tests-backend.yml new file mode 100644 index 0000000000..fb58ed7ea4 --- /dev/null +++ b/.github/workflows/tests-backend.yml @@ -0,0 +1,84 @@ +name: "CI: Backend" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'backend/**' + - 'common/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'backend/**' + - 'common/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-backend: + if: ${{ !github.event.pull_request.draft }} + name: "Backend Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + services: + postgres: + image: postgres:17 + # Provide the password for postgres + env: + POSTGRES_USER: penpot_test + POSTGRES_PASSWORD: penpot_test + POSTGRES_DB: penpot_test + + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + redis: + image: valkey/valkey:9 + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint + working-directory: ./backend + run: | + corepack enable; + corepack install; + pnpm install; + pnpm run check-fmt + pnpm run lint + + - name: Tests + working-directory: ./backend + env: + PENPOT_TEST_DATABASE_URI: "postgresql://postgres/penpot_test" + PENPOT_TEST_DATABASE_USERNAME: penpot_test + PENPOT_TEST_DATABASE_PASSWORD: penpot_test + PENPOT_TEST_REDIS_URI: "redis://redis/1" + + run: | + mkdir -p /tmp/penpot; + clojure -M:dev:test --reporter kaocha.report/documentation diff --git a/.github/workflows/tests-common.yml b/.github/workflows/tests-common.yml new file mode 100644 index 0000000000..5996c82742 --- /dev/null +++ b/.github/workflows/tests-common.yml @@ -0,0 +1,57 @@ +name: "CI: Common" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'common/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'common/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-common: + if: ${{ !github.event.pull_request.draft }} + name: "Common Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint + working-directory: ./common + run: | + corepack enable; + corepack install; + pnpm install; + pnpm run check-fmt:clj + pnpm run check-fmt:js + pnpm run lint:clj + + - name: Tests + working-directory: ./common + run: | + ./scripts/test diff --git a/.github/workflows/tests-frontend.yml b/.github/workflows/tests-frontend.yml new file mode 100644 index 0000000000..14011a5110 --- /dev/null +++ b/.github/workflows/tests-frontend.yml @@ -0,0 +1,71 @@ +name: "CI: Frontend" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'frontend/**' + - 'common/**' + - 'render-wasm/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'frontend/**' + - 'common/**' + - 'render-wasm/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-frontend: + if: ${{ !github.event.pull_request.draft }} + name: "Frontend Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint + working-directory: ./frontend + run: | + corepack enable; + corepack install; + pnpm install; + pnpm run check-fmt:js + pnpm run check-fmt:clj + pnpm run check-fmt:scss + pnpm run lint:clj + pnpm run lint:js + pnpm run lint:scss + + - name: Unit Tests + working-directory: ./frontend + run: | + ./scripts/test + + - name: Component Tests + working-directory: ./frontend + env: + VITEST_BROWSER_TIMEOUT: 120000 + run: | + ./scripts/test-components diff --git a/.github/workflows/tests-integration.yml b/.github/workflows/tests-integration.yml new file mode 100644 index 0000000000..b028676aa7 --- /dev/null +++ b/.github/workflows/tests-integration.yml @@ -0,0 +1,93 @@ +name: "CI: Integration" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'frontend/**' + - 'common/**' + - 'render-wasm/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'frontend/**' + - 'common/**' + - 'render-wasm/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build-integration: + if: ${{ !github.event.pull_request.draft }} + name: "Build Integration Bundle" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Build Bundle + working-directory: ./frontend + run: | + ./scripts/build + + - name: Store Bundle Cache + uses: actions/cache@v5 + with: + key: "integration-bundle-${{ github.sha }}" + path: frontend/resources/public + + test-integration: + if: ${{ !github.event.pull_request.draft }} + name: "Integration Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + needs: build-integration + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Restore Cache + uses: actions/cache/restore@v5 + with: + key: "integration-bundle-${{ github.sha }}" + path: frontend/resources/public + + - name: Run Tests + working-directory: ./frontend + run: | + ./scripts/test-e2e + + - name: Upload test result + uses: actions/upload-artifact@v7 + if: always() + with: + name: integration-tests-result + path: frontend/test-results/ + overwrite: true + retention-days: 3 diff --git a/.github/workflows/tests-library.yml b/.github/workflows/tests-library.yml new file mode 100644 index 0000000000..4c84965f4c --- /dev/null +++ b/.github/workflows/tests-library.yml @@ -0,0 +1,58 @@ +name: "CI: Library" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'common/**' + - 'library/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'common/**' + - 'library/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-library: + if: ${{ !github.event.pull_request.draft }} + name: "Library Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Lint + working-directory: ./library + run: | + corepack enable; + corepack install; + pnpm install; + pnpm run check-fmt + pnpm run lint + + - name: Tests + working-directory: ./library + run: | + ./scripts/test diff --git a/.github/workflows/tests-mcp.yml b/.github/workflows/tests-mcp.yml index 0ab2909b72..9e622ca83a 100644 --- a/.github/workflows/tests-mcp.yml +++ b/.github/workflows/tests-mcp.yml @@ -45,3 +45,8 @@ jobs: pnpm run fmt:check; pnpm -r run build; pnpm -r run types:check; + + - name: Tests + working-directory: ./mcp + run: | + pnpm -r run test; diff --git a/.github/workflows/tests-plugins.yml b/.github/workflows/tests-plugins.yml new file mode 100644 index 0000000000..5cc161461c --- /dev/null +++ b/.github/workflows/tests-plugins.yml @@ -0,0 +1,83 @@ +name: "CI: Plugins" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'plugins/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'plugins/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-plugins: + if: ${{ !github.event.pull_request.draft }} + name: Plugins Runtime Linter & Tests + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - uses: actions/checkout@v6 + + - name: Setup Node + id: setup-node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + + - name: Install deps + working-directory: ./plugins + shell: bash + run: | + corepack enable; + corepack install; + pnpm install; + + - name: Run Lint + working-directory: ./plugins + run: pnpm run lint + + - name: Run Format Check + working-directory: ./plugins + run: pnpm run format:check + + - name: Run Test + working-directory: ./plugins + run: pnpm run test + + - name: Build runtime + working-directory: ./plugins + run: pnpm run build:runtime + + - name: Build doc + working-directory: ./plugins + run: pnpm run build:doc + + - name: Build plugins + working-directory: ./plugins + run: pnpm run build:plugins + + - name: Build styles + working-directory: ./plugins + run: pnpm run build:styles-example diff --git a/.github/workflows/tests-wasm.yml b/.github/workflows/tests-wasm.yml new file mode 100644 index 0000000000..424d4f908f --- /dev/null +++ b/.github/workflows/tests-wasm.yml @@ -0,0 +1,57 @@ +name: "CI: WASM" + +defaults: + run: + shell: bash + +on: + pull_request: + paths: + - 'render-wasm/**' + + types: + - opened + - synchronize + - ready_for_review + + push: + branches: + - develop + - staging + + paths: + - 'render-wasm/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-render-wasm: + if: ${{ !github.event.pull_request.draft }} + name: "Render WASM Tests" + runs-on: penpot-runner-02 + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Format + working-directory: ./render-wasm + run: | + cargo fmt --check + + - name: Lint + working-directory: ./render-wasm + run: | + ./lint + + - name: Test + working-directory: ./render-wasm + run: | + ./test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 4bbe6ddd3b..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,411 +0,0 @@ -name: "CI" - -defaults: - run: - shell: bash - -on: - pull_request: - types: - - opened - - synchronize - - ready_for_review - push: - branches: - - develop - - staging - -concurrency: - group: ${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - lint: - if: ${{ !github.event.pull_request.draft }} - name: "Linter" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Lint Common - working-directory: ./common - run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt:clj - pnpm run check-fmt:js - pnpm run lint:clj - - - name: Lint Frontend - working-directory: ./frontend - run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt:js - pnpm run check-fmt:clj - pnpm run check-fmt:scss - pnpm run lint:clj - pnpm run lint:js - pnpm run lint:scss - - - name: Lint Backend - working-directory: ./backend - run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt - pnpm run lint - - - name: Lint Exporter - working-directory: ./exporter - run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt - pnpm run lint - - - name: Lint Library - working-directory: ./library - run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt - pnpm run lint - - test-common: - if: ${{ !github.event.pull_request.draft }} - name: "Common Tests" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Run tests - working-directory: ./common - run: | - ./scripts/test - - test-plugins: - if: ${{ !github.event.pull_request.draft }} - name: Plugins Runtime Linter & Tests - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - uses: actions/checkout@v6 - - - name: Setup Node - id: setup-node - uses: actions/setup-node@v6 - with: - node-version-file: .nvmrc - - - name: Install deps - working-directory: ./plugins - shell: bash - run: | - corepack enable; - corepack install; - pnpm install; - - - name: Run Lint - working-directory: ./plugins - run: pnpm run lint - - - name: Run Format Check - working-directory: ./plugins - run: pnpm run format:check - - - name: Run Test - working-directory: ./plugins - run: pnpm run test - - - name: Build runtime - working-directory: ./plugins - run: pnpm run build:runtime - - - name: Build doc - working-directory: ./plugins - run: pnpm run build:doc - - - name: Build plugins - working-directory: ./plugins - run: pnpm run build:plugins - - - name: Build styles - working-directory: ./plugins - run: pnpm run build:styles-example - - test-frontend: - if: ${{ !github.event.pull_request.draft }} - name: "Frontend Tests" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Unit Tests - working-directory: ./frontend - run: | - ./scripts/test - - - name: Component Tests - working-directory: ./frontend - env: - VITEST_BROWSER_TIMEOUT: 120000 - run: | - ./scripts/test-components - - test-render-wasm: - if: ${{ !github.event.pull_request.draft }} - name: "Render WASM Tests" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Format - working-directory: ./render-wasm - run: | - cargo fmt --check - - - name: Lint - working-directory: ./render-wasm - run: | - ./lint - - - name: Test - working-directory: ./render-wasm - run: | - ./test - - test-backend: - if: ${{ !github.event.pull_request.draft }} - name: "Backend Tests" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - services: - postgres: - image: postgres:17 - # Provide the password for postgres - env: - POSTGRES_USER: penpot_test - POSTGRES_PASSWORD: penpot_test - POSTGRES_DB: penpot_test - - # Set health checks to wait until postgres has started - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - redis: - image: valkey/valkey:9 - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Run tests - working-directory: ./backend - env: - PENPOT_TEST_DATABASE_URI: "postgresql://postgres/penpot_test" - PENPOT_TEST_DATABASE_USERNAME: penpot_test - PENPOT_TEST_DATABASE_PASSWORD: penpot_test - PENPOT_TEST_REDIS_URI: "redis://redis/1" - - run: | - mkdir -p /tmp/penpot; - clojure -M:dev:test --reporter kaocha.report/documentation - - test-library: - if: ${{ !github.event.pull_request.draft }} - name: "Library Tests" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Run tests - working-directory: ./library - run: | - ./scripts/test - - build-integration: - if: ${{ !github.event.pull_request.draft }} - name: "Build Integration Bundle" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Build Bundle - working-directory: ./frontend - run: | - ./scripts/build - - - name: Store Bundle Cache - uses: actions/cache@v5 - with: - key: "integration-bundle-${{ github.sha }}" - path: frontend/resources/public - - test-integration-1: - if: ${{ !github.event.pull_request.draft }} - name: "Integration Tests 1/3" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - needs: build-integration - - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - - - name: Restore Cache - uses: actions/cache/restore@v5 - with: - key: "integration-bundle-${{ github.sha }}" - path: frontend/resources/public - - - name: Run Tests - working-directory: ./frontend - run: | - ./scripts/test-e2e --shard="1/3"; - - - name: Upload test result - uses: actions/upload-artifact@v7 - if: always() - with: - name: integration-tests-result-1 - path: frontend/test-results/ - overwrite: true - retention-days: 3 - - test-integration-2: - if: ${{ !github.event.pull_request.draft }} - name: "Integration Tests 2/3" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - needs: build-integration - - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - - - name: Restore Cache - uses: actions/cache/restore@v5 - with: - key: "integration-bundle-${{ github.sha }}" - path: frontend/resources/public - - - name: Run Tests - working-directory: ./frontend - run: | - ./scripts/test-e2e --shard="2/3"; - - - name: Upload test result - uses: actions/upload-artifact@v7 - if: always() - with: - name: integration-tests-result-2 - path: frontend/test-results/ - overwrite: true - retention-days: 3 - - test-integration-3: - if: ${{ !github.event.pull_request.draft }} - name: "Integration Tests 3/3" - runs-on: penpot-runner-02 - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - needs: build-integration - - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - - - name: Restore Cache - uses: actions/cache/restore@v5 - with: - key: "integration-bundle-${{ github.sha }}" - path: frontend/resources/public - - - name: Run Tests - working-directory: ./frontend - run: | - ./scripts/test-e2e --shard="3/3"; - - - name: Upload test result - uses: actions/upload-artifact@v7 - if: always() - with: - name: integration-tests-result-3 - path: frontend/test-results/ - overwrite: true - retention-days: 3 diff --git a/.opencode/skills/create-pr/SKILL.md b/.opencode/skills/create-pr/SKILL.md new file mode 100644 index 0000000000..88b5464481 --- /dev/null +++ b/.opencode/skills/create-pr/SKILL.md @@ -0,0 +1,81 @@ +--- +name: create-pr +description: Create a GitHub PR following Penpot conventions, with a concise engineer-focused description +--- + +# Create Pull Request + +Create a GitHub PR with proper title format and a concise description that explains reasoning, not implementation details. + +## When to Use + +- Opening a new pull request +- The user asks to create a PR +- Code changes are ready and committed + +## Workflow + +### 1. Verify Prerequisites + +```bash +git branch --show-current +git log --oneline main..HEAD +``` + +### 2. Check if Branch is Pushed + +```bash +BRANCH=$(git branch --show-current) +if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then + echo "Branch is pushed, proceeding with PR creation" +else + echo "ERROR: Branch '$BRANCH' is not pushed to remote. Please push the branch first." + exit 1 +fi +``` + +**If the branch is not pushed, STOP here and ask the user to push it. The LLM does not have push permissions.** + +### 3. Create PR Body + +Write to `/tmp/pr-body.md` to avoid shell quoting issues: + +```bash +cat > /tmp/pr-body.md << 'EOF' +**Note:** This PR was created with AI assistance. + +## What + + + +## Why + + + +## How + + +EOF +``` + +### 4. Create the PR + +Follow title and description format from `mem:workflow/creating-prs` and `mem:workflow/creating-commits`. + +```bash +gh pr create --base main --project "Main" --title "" --body-file /tmp/pr-body.md +``` + +### 5. What NOT to Include + +- ❌ List of files changed (visible in diff) +- ❌ Testing steps (CI handles this) +- ❌ Screenshots unless UI-visible +- ❌ Migration notes unless breaking changes +- ❌ Regression fixes introduced during the PR (they're part of the development process, not the feature) + +## Key Principles + +- **Write for humans.** The diff shows what changed. The description explains why. +- **Be concise.** Focus on reasoning: What was the problem? Why did it happen? How did you solve it? +- **Skip the obvious.** Don't explain what `git diff` already shows. diff --git a/.opencode/skills/gh-issue-from-pr/SKILL.md b/.opencode/skills/gh-issue-from-pr/SKILL.md index 012d58f7be..bb7fe10cf9 100644 --- a/.opencode/skills/gh-issue-from-pr/SKILL.md +++ b/.opencode/skills/gh-issue-from-pr/SKILL.md @@ -39,8 +39,8 @@ Identify: | Field | Source | Rule | |-------|--------|------| -| **Title** | PR title | Rewrite from user perspective. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on observable behavior. Use imperative mood. | -| **Labels** | PR labels | Copy user-facing labels (`bug`, `enhancement`, `community contribution`). Skip workflow labels (`backport candidate`, `team-qa`). | +| **Title** | PR title | Rewrite from user perspective. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on observable behavior. Use imperative mood. Use the `issue-title` skill to generate this. | +| **Labels** | PR labels | Copy `community contribution` if present. Skip `bug` and `enhancement` (redundant with Issue Type). Skip workflow labels (`backport candidate`, `team-qa`). | | **Milestone** | PR milestone | **Always copy what's on the PR.** Fetch with: `gh pr view <PR_NUMBER> --json milestone --jq '.milestone.title'` If the PR has no milestone, create the issue without one. | | **Project** | Always `Main` | Penpot uses the `Main` project (number 8) for all issues. | | **Body** | PR's user-facing section | Extract steps to reproduce or feature description. Omit internal details. Use templates below. | @@ -101,8 +101,7 @@ Create: gh issue create \ --repo penpot/penpot \ --title "<Title>" \ - --label "<label1>" \ - --label "<label2>" \ + --label "community contribution" \ # only if PR has this label --milestone "<milestone>" \ --project "Main" \ --body-file /tmp/issue-body.md @@ -198,12 +197,10 @@ rm -f /tmp/issue-body.md /tmp/pr-body.md | PR has | Issue gets | |--------|-----------| -| `bug` | `bug` | -| `enhancement` | `enhancement` | | `community contribution` | `community contribution` | +| `bug`, `enhancement` | *(skip — redundant with Issue Type)* | | `backport candidate` | *(skip — workflow label)* | | `team-qa` | *(skip — workflow label)* | -| No user-facing label | Infer from title: `:bug:` → `bug`, `:sparkles:` → `enhancement` | ## Issue Type mapping diff --git a/.opencode/skills/update-changelog/SKILL.md b/.opencode/skills/update-changelog/SKILL.md index 9789eb2f18..73d487053b 100644 --- a/.opencode/skills/update-changelog/SKILL.md +++ b/.opencode/skills/update-changelog/SKILL.md @@ -48,7 +48,7 @@ python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" **Exclusion rules (issue-level):** - `no changelog` label — Chore/refactor work that doesn't need a changelog entry - `release blocker` label — Blocked issues not yet ready for changelog -- `Task` issue type — Internal chores are not user-facing; filter these out after fetching +- `Task` issue type — Internal chores are not user-facing; automatically excluded by `gh.py`. Use `--include-tasks` to override. - **Rejected project status** — Issues with a "Rejected" status in the "Main" project board are automatically excluded by `gh.py`. This project-level status (independent of the GitHub issue `state`) indicates the issue was rejected from the release. Use `--include-rejected` to override. **Exclusion rules (PR-level):** @@ -347,71 +347,233 @@ if closed: - <fix description> (by @contributor) [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>)) ``` -### 10. Cross-reference milestone PRs against the changelog +### 11. Generate anomaly report and save to CHANGES-ISSUES.md -Issues can be fixed by PRs that aren't in the milestone, and merged PRs in -the milestone may not close any tracked issue. After writing, run a full -cross-reference to catch gaps: +After all edits and cross-referencing are complete, generate a structured +anomaly report and save it to `CHANGES-ISSUES.md` (overwriting if exists). +This provides a persistent record of any discrepancies between the milestone +and the changelog. + +Run this self-contained script: ```bash -# List all merged PRs in the milestone -python3 tools/gh.py prs --milestone "<MILESTONE>" --state merged > /tmp/milestone-prs.json +python3 << 'PYEOF' +import json, re, subprocess, sys -# Extract PR numbers from the changelog section -python3 -c " -import json, re +MILESTONE = "<MILESTONE>" +CHANGES_MD = "CHANGES.md" +OUTPUT = "CHANGES-ISSUES.md" -with open('CHANGES.md') as f: +# Fetch milestone issues (all states) +result = subprocess.run( + ["python3", "tools/gh.py", "issues", MILESTONE, "--state", "all"], + capture_output=True, text=True) +all_issues = json.loads(result.stdout) +issue_by_num = {i['number']: i for i in all_issues} + +# Fetch milestone PRs (all states) +result = subprocess.run( + ["python3", "tools/gh.py", "prs", "--milestone", MILESTONE, "--state", "all"], + capture_output=True, text=True) +all_prs = json.loads(result.stdout) +pr_by_num = {p['number']: p for p in all_prs} + +# Read changelog +with open(CHANGES_MD) as f: content = f.read() -# Extract the version section (adjust regex to match the actual version) -match = re.search(r'## <MILESTONE> \(Unreleased\)\n(.*?)(?:\n## |\Z)', content, re.DOTALL) -section = match.group(1) +m = re.search(rf'## {MILESTONE} \(Unreleased\)\n(.*?)(?:\n## |\Z)', content, re.DOTALL) +section = m.group(1) if m else "" + +# Collect issue and PR references from the changelog section +changelog_issues = set() +for num in re.findall(r'\[#(\d+)\]\(https://github\.com/penpot/penpot/issues/\d+\)', section): + changelog_issues.add(int(num)) +for num in re.findall(r'\[Github #(\d+)\]', section): + changelog_issues.add(int(num)) -# Collect all PR numbers referenced changelog_prs = set() -for m in re.findall(r'\[#(\d+)\]\(https://github\.com/penpot/penpot/pull/\d+\)', section): - changelog_prs.add(int(m)) +for num in re.findall(r'\[#(\d+)\]\(https://github\.com/penpot/penpot/pull/\d+\)', section): + changelog_prs.add(int(num)) +for num in re.findall(r'PR:\[(\d+)\]', section): + changelog_prs.add(int(num)) -# Collect all milestone PRs (filtered) -with open('/tmp/milestone-prs.json') as f: - milestone_prs = json.load(f) +# Determine valid (non-excluded) milestone issues +EXCLUDED_LABELS = {'release blocker', 'no changelog'} +EXCLUDED_ISSUE_TYPES = {'Task'} +EXCLUDED_PROJECT_STATUS = {'Rejected'} -milestone_merged = {pr['number'] for pr in milestone_prs} +valid_issues = [] +for issue in all_issues: + labels = set(issue.get('labels', [])) + if issue.get('state') != 'CLOSED': continue + if issue.get('issue_type') in EXCLUDED_ISSUE_TYPES: continue + if issue.get('project_status') in EXCLUDED_PROJECT_STATUS: continue + if EXCLUDED_LABELS & labels: continue + valid_issues.append(issue) +valid_nums = {i['number'] for i in valid_issues} -# PRs in milestone but not in changelog -missing = sorted(milestone_merged - changelog_prs) -print(f'Milestone merged PRs: {len(milestone_merged)}') -print(f'Changelog referenced PRs: {len(changelog_prs)}') -print(f'PRs in milestone but NOT in changelog: {len(missing)}') -for num in missing: - pr = next(p for p in milestone_prs if p['number'] == num) - print(f' #{num} {pr[\"title\"][:80]}') -" +# --- Gather anomalies --- +anomalies = [] + +# Type 1: Entries in changelog that should be excluded +for num in sorted(changelog_issues): + issue = issue_by_num.get(num) + if issue is None: + anomalies.append({ + 'type': 'should_remove', + 'severity': 'HIGH', + 'number': num, + 'title': '', + 'reason': 'Issue not found in milestone (deleted or moved)' + }) + continue + labels = set(issue.get('labels', [])) + reasons = [] + if issue.get('state') != 'CLOSED': + reasons.append(f'state is "{issue["state"]}" (should be CLOSED)') + if 'release blocker' in labels: + reasons.append('has "release blocker" label') + if 'no changelog' in labels: + reasons.append('has "no changelog" label') + if issue.get('issue_type') == 'Task': + reasons.append(f'issue_type is Task (internal chore)') + if issue.get('project_status') == 'Rejected': + reasons.append('project_status is Rejected') + if reasons: + anomalies.append({ + 'type': 'should_remove', + 'severity': 'MEDIUM' if issue.get('issue_type') == 'Task' else 'HIGH', + 'number': num, + 'title': issue.get('title', '')[:80], + 'reason': '; '.join(reasons) + }) + +# Type 2: Valid issues not in changelog +for num in sorted(valid_nums - changelog_issues): + issue = issue_by_num[num] + info = { + 'type': 'missing', + 'severity': 'MEDIUM', + 'number': num, + 'title': issue['title'][:80], + 'issue_type': issue['issue_type'], + 'closing_prs': issue.get('closing_prs', []), + 'note': '' + } + # Check for duplicate (same PR as existing entry) + existing = [] + for pr_num in issue.get('closing_prs', []): + for cl_num in changelog_issues: + cl_issue = issue_by_num.get(cl_num) + if cl_issue and pr_num in cl_issue.get('closing_prs', []): + existing.append(f'#{cl_num}') + if existing: + info['note'] = f'DUPLICATE: same PR as existing entry(ies): {", ".join(existing)}' + # Check closing PRs not merged + unmerged = [] + for pr_num in issue.get('closing_prs', []): + pr = pr_by_num.get(pr_num) + if pr is None: + unmerged.append(f'#{pr_num} (unknown)') + elif pr.get('state') != 'MERGED': + unmerged.append(f'#{pr_num} (state={pr["state"]})') + if unmerged: + info['note'] = (info['note'] + '; ' if info['note'] else '') + f'Closing PRs not merged: {", ".join(unmerged)}' + anomalies.append(info) + +# Type 3: PRs in changelog that are not merged +for pr_num in sorted(changelog_prs): + pr = pr_by_num.get(pr_num) + if pr is None: + anomalies.append({ + 'type': 'unmerged_pr', + 'severity': 'HIGH', + 'number': pr_num, + 'title': '', + 'reason': 'PR not found in milestone PR list' + }) + elif pr.get('state') != 'MERGED': + anomalies.append({ + 'type': 'unmerged_pr', + 'severity': 'HIGH', + 'number': pr_num, + 'title': pr.get('title', '')[:80], + 'reason': f'state={pr["state"]} (should be MERGED)' + }) + +# --- Write report to CHANGES-ISSUES.md --- +with open(OUTPUT, 'w') as f: + f.write(f'# Changelog Anomaly Report — {MILESTONE}\n\n') + f.write(f'Generated: {__import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M UTC")}\n\n') + f.write('---\n\n') + + # Summary + n_remove = sum(1 for a in anomalies if a['type'] == 'should_remove') + n_missing = sum(1 for a in anomalies if a['type'] == 'missing') + n_pr = sum(1 for a in anomalies if a['type'] == 'unmerged_pr') + f.write(f'## Summary\n\n') + f.write(f'- **Issues to remove from changelog:** {n_remove}\n') + f.write(f'- **Valid issues missing from changelog:** {n_missing}\n') + f.write(f'- **Unmerged PRs referenced:** {n_pr}\n') + f.write(f'- **Total anomalies:** {len(anomalies)}\n\n') + + if not anomalies: + f.write('✅ No anomalies found. The changelog is fully consistent with the milestone.\n\n') + else: + # Type 1 + if n_remove: + f.write(f'## Issues to Remove\n\n') + f.write('These entries are in the changelog but should be excluded based on current issue metadata.\n\n') + for a in anomalies: + if a['type'] != 'should_remove': continue + badge = '🔴' if a['severity'] == 'HIGH' else '🟡' + f.write(f'{badge} **#{a["number"]}**') + if a.get('title'): f.write(f' — {a["title"]}') + f.write(f'\n - Reason: {a["reason"]}\n\n') + + # Type 2 + if n_missing: + f.write(f'## Valid Issues Not in Changelog\n\n') + f.write('These issues are closed, non-excluded milestone items that lack a changelog entry.\n\n') + for a in anomalies: + if a['type'] != 'missing': continue + f.write(f'❓ **#{a["number"]}** — {a["title"]}\n') + f.write(f' - Type: {a["issue_type"]}, Closing PRs: {a["closing_prs"]}\n') + if a.get('note'): f.write(f' - Note: {a["note"]}\n') + f.write('\n') + + # Type 3 + if n_pr: + f.write(f'## Unmerged PRs Referenced in Changelog\n\n') + f.write('These PR numbers appear in the changelog but are not merged.\n\n') + for a in anomalies: + if a['type'] != 'unmerged_pr': continue + f.write(f'🔴 **#{a["number"]}**') + if a.get('title'): f.write(f' — {a["title"]}') + f.write(f'\n - {a["reason"]}\n\n') + + # Appendix: counts + f.write('---\n\n') + f.write(f'## Context\n\n') + f.write(f'- Milestone total issues (all states): {len(all_issues)}\n') + f.write(f'- Valid issues after exclusions: {len(valid_issues)}\n') + f.write(f'- Issues referenced in changelog: {len(changelog_issues)}\n') + f.write(f'- PRs referenced in changelog: {len(changelog_prs)}\n') + +print(f"Anomaly report written to {OUTPUT}") +PYEOF ``` -For each missing PR found, decide whether it should be added to the -changelog or is legitimately excluded (check its labels). +This generates `CHANGES-ISSUES.md` with three sections: +1. **Issues to Remove** — Entries in the changelog that should be excluded based + on current issue metadata (labels, type, project status, or deletion). +2. **Valid Issues Not in Changelog** — Closed, non-excluded milestone issues + that lack a changelog entry (with notes on duplicates and unmerged closing PRs). +3. **Unmerged PRs Referenced** — PRs in the changelog that are not merged. -Also verify that no closed-unmerged PRs remain in the changelog: - -```bash -python3 tools/gh.py prs --milestone "<MILESTONE>" --state all | python3 -c " -import json, sys -data = json.load(sys.stdin) -closed = [p for p in data if p['state'] == 'CLOSED'] -if closed: - print('WARNING: CLOSED (unmerged) PRs in milestone:') - for p in closed: - print(f' #{p[\"number\"]} {p[\"title\"][:80]}') -" -``` - -**Post-edit audit checklist:** -- ✅ All referenced PRs are merged (no closed-unmerged artifacts) -- ✅ Every merged milestone PR is either in the changelog or excluded by label -- ✅ PR and issue counts are internally consistent -- ✅ No false-positive PR-to-issue associations +The report is overwritten each time it's generated, reflecting the current +state of the milestone and changelog. ## Key Principles diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 17bb15bc55..496bcf6f2e 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -9,7 +9,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. # Development workflow -- Commit only when explicitly asked. Commit/PR format + changelog: `mem:workflow/creating-commits`, `mem:workflow/creating-prs`. +- Commit only when explicitly asked. Commit/PR format + changelog: `mem:workflow/creating-commits`, `mem:workflow/creating-prs`. Issue creation (titles, labels, body templates, Issue Types): `mem:workflow/creating-issues`. - You have access to the GitHub CLI `gh` or corresponding MCP tools. - Issues are also managed on Taiga. Read issues using the `read_taiga_issue` tool. - Before writing code, analyze the task in depth and describe your plan. If the task is complex, break it down into atomic steps. diff --git a/.serena/memories/workflow/creating-issues.md b/.serena/memories/workflow/creating-issues.md new file mode 100644 index 0000000000..42a07f7887 --- /dev/null +++ b/.serena/memories/workflow/creating-issues.md @@ -0,0 +1,160 @@ +# Creating Issues + +Create GitHub issues only on explicit request. Use `gh` CLI authenticated to `penpot/penpot`. + +## Title Derivation + +Derive the title from the source material (bug report, user feedback, feature request, etc.) — not from any pre-existing title which may be auto-generated or stale. + +### Bug titles (descriptive present tense) + +Describe the symptom as it appears to the user. Format: `[Where] [present-tense verb] when [condition]`. + +- *"Plugin API crashes when setting text fills"* +- *"Canvas renders glitches when zooming quickly"* +- *"French Canada locale falls back to French (fr) translations"* + +Do **not** start bug titles with "Fix" or any imperative verb — state what's broken, not command a fix. + +### Feature / Enhancement titles (imperative mood) + +Command what should be built. Format: `[Imperative verb] [what] in/on [where]`. + +- *"Add customizable dash and gap length controls to dashed strokes in the sidebar"* +- *"Show user, timestamp, and hash in the workspace history panel like git commits"* + +### Universal rules + +- **Include the "where"** — specify the UI location or module (e.g. "in the sidebar", "on the stroke options") +- **No prefixes** — strip `bug:`, `feature:`, `feat:`, `:bug:`, `:sparkles:`, `[PENPOT FEEDBACK]`, etc. +- **No emoji** — plain text only +- **Be specific** — prefer concrete detail over generality +- **Two problems → cover both** — if the description has two distinct but related issues, capture both joined by "and" + +## Metadata + +| Field | Rule | +|-------|------| +| **Labels** | `bug` (crashes/regressions) · `enhancement` (new features) · `community contribution` (PRs from non-core) · skip workflow labels (`backport candidate`, `team-qa`) | +| **Milestone** | Use the current or next planned milestone. Fetch available milestones: `gh api repos/penpot/penpot/milestones --jq '.[].title'`. If unsure, omit. | +| **Project** | Always `Main` (project number 8). Use `--project "Main"` flag. | +| **Issue Type** | See Issue Type section below. Cannot be set via `gh issue create` — use GraphQL after creation. | + +## Issue Body Template + +Write the body to a temp file to avoid shell quoting issues: + +**Bug template:** +```markdown +### Description + +<what breaks, what the user experiences> + +### Steps to reproduce + +1. <step 1> +2. <step 2> + +### Expected behavior + +<what should happen instead> + +### Affected versions + +<version> +``` + +**Enhancement template:** +```markdown +### Description + +<what the user can now do that they couldn't before> + +### Use case + +<why this is useful, who benefits> + +### Affected versions + +<version> +``` + +## Creating the Issue + +```bash +cat > /tmp/issue-body.md << 'ISSUE_BODY' +<body content here> +ISSUE_BODY + +gh issue create \ + --repo penpot/penpot \ + --title "<Derived title>" \ + --label "<label>" \ + --project "Main" \ + --body-file /tmp/issue-body.md +``` + +Output: `https://github.com/penpot/penpot/issues/<NUMBER>` + +## Setting the Issue Type + +`gh issue create` can't set Issue Type directly. Use GraphQL after creation. + +**Issue Type IDs for penpot/penpot:** + +| Type | ID | +|------|----| +| Bug | `IT_kwDOAcyBPM4AX5Nb` | +| Enhancement | `IT_kwDOAcyBPM4B_IQN` | +| Feature | `IT_kwDOAcyBPM4AX5Nf` | +| Task | `IT_kwDOAcyBPM4AX5NY` | +| Question | `IT_kwDOAcyBPM4B_IQj` | +| Docs | `IT_kwDOAcyBPM4B_IQz` | + +**Map:** +- `bug` label → Bug +- `enhancement` label → Enhancement +- Feature/epic → Feature +- Docs → Docs +- None of the above → Task + +**Set it:** +```bash +ISSUE_ID=$(gh api graphql -f query=' +query { repository(owner: "penpot", name: "penpot") { + issue(number: <NUMBER>) { id } +}}' --jq '.data.repository.issue.id') + +gh api graphql -f query=' +mutation { + updateIssue(input: { + id: "'"$ISSUE_ID"'" + issueTypeId: "<TYPE_ID>" + }) { + issue { number issueType { name } } + } +}' +``` + +## Verification + +```bash +gh issue view <NUMBER> --repo penpot/penpot \ + --json title,labels,milestone,projectItems \ + --jq '{title, milestone: .milestone.title, labels: [.labels[].name], projects: [.projectItems[].title]}' + +gh api graphql -f query=' +query { repository(owner: "penpot", name: "penpot") { + issue(number: <NUMBER>) { issueType { name } } +}}' --jq '.data.repository.issue.issueType.name' +``` + +## Cleanup + +```bash +rm -f /tmp/issue-body.md +``` + +## See Also + +- Creating issues **from PRs** (separating WHAT from HOW): `mem:workflow/creating-prs` diff --git a/CHANGES.md b/CHANGES.md index 3c1bb597d3..58d54e8199 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,16 @@ - Remove stray debug log in exporter upload-resource (by @iot2edge) [#9270](https://github.com/penpot/penpot/issues/9270) (PR: [#9272](https://github.com/penpot/penpot/pull/9272)) - Release pool connection during font variant creation (by @Dexterity104) [#9286](https://github.com/penpot/penpot/issues/9286) (PR: [#9287](https://github.com/penpot/penpot/pull/9287)) - Add autocomplete combobox to token creation and edition forms [#9899](https://github.com/penpot/penpot/issues/9899) (PR: [#9109](https://github.com/penpot/penpot/pull/9109)) +- Add list view mode to color picker UI [#4420](https://github.com/penpot/penpot/issues/4420) (PR: [#9953](https://github.com/penpot/penpot/pull/9953)) +- Use Clipboard API consistently across the application (by @MilosM348) [#6514](https://github.com/penpot/penpot/issues/6514) (PR: [#9188](https://github.com/penpot/penpot/pull/9188)) +- Use `$` as DTCG token/group discriminator and make `$description` optional [#8342](https://github.com/penpot/penpot/issues/8342) (PR: [#9912](https://github.com/penpot/penpot/pull/9912)) +- Match version preview banner text to History sidebar labels (by @MilosM348) [#9503](https://github.com/penpot/penpot/issues/9503) (PR: [#9697](https://github.com/penpot/penpot/pull/9697)) +- Use "copia" as duplicate suffix for Spanish (by @Rene0422) [#9623](https://github.com/penpot/penpot/issues/9623) (PR: [#9671](https://github.com/penpot/penpot/pull/9671)) +- Harden CORS middleware to not reflect Origin with credentials enabled [#9659](https://github.com/penpot/penpot/issues/9659) (PR: [#9675](https://github.com/penpot/penpot/pull/9675)) +- Revert token migrations on clashing names to prevent data loss [#9816](https://github.com/penpot/penpot/issues/9816) (PR: [#9950](https://github.com/penpot/penpot/pull/9950)) +- Update contributing guidelines with current issue tags and CSS linting rules [#9900](https://github.com/penpot/penpot/issues/9900) (PR: [#9418](https://github.com/penpot/penpot/pull/9418)) +- Add composite typography token input to the Design sidebar [#9932](https://github.com/penpot/penpot/issues/9932) (PR: [#9128](https://github.com/penpot/penpot/pull/9128), [#9375](https://github.com/penpot/penpot/pull/9375), [#8749](https://github.com/penpot/penpot/pull/8749)) +- Avoid deduplicating temporary export files to prevent stale content (by @yong2bba) [#9970](https://github.com/penpot/penpot/issues/9970) (PR: [#9959](https://github.com/penpot/penpot/pull/9959)) ### :bug: Bugs fixed @@ -63,8 +73,46 @@ - Fix plugin `addInteraction` silently rejecting `open-overlay` actions with `manualPositionLocation` [Github #8409](https://github.com/penpot/penpot/issues/8409) - Fix typography style creation with tokenized line-height (by @juan-flores077) [Github #8479](https://github.com/penpot/penpot/issues/8479) - Fix colorpicker layout so the eyedropper button is visible again [Taiga #14057](https://tree.taiga.io/project/penpot/issue/14057) +- Fix SVG stroke line join not applied when pasting strokes [#4836](https://github.com/penpot/penpot/issues/4836) (PR: [#9982](https://github.com/penpot/penpot/pull/9982), [#10019](https://github.com/penpot/penpot/pull/10019)) +- Fix blend-mode hover preview on canvas not reverted when dismissing dropdown (by @jack-stormentswe) [#9235](https://github.com/penpot/penpot/issues/9235) (PR: [#9237](https://github.com/penpot/penpot/pull/9237)) +- Fix View Mode mouse-leave and click in combination not working [#4855](https://github.com/penpot/penpot/issues/4855) (PR: [#9991](https://github.com/penpot/penpot/pull/9991)) +- Fix Storybook UI missing scrollbar (by @MilosM348) [#6049](https://github.com/penpot/penpot/issues/6049) (PR: [#9319](https://github.com/penpot/penpot/pull/9319)) +- Fix font selector missing intermediate font weights for Source Sans Pro and similar fonts (by @dhgoal) [#7378](https://github.com/penpot/penpot/issues/7378) (PR: [#9247](https://github.com/penpot/penpot/pull/9247)) +- Fix plugin API `typography.remove()` passing wrong parameter format (by @leonaIee) [#8223](https://github.com/penpot/penpot/issues/8223) (PR: [#9279](https://github.com/penpot/penpot/pull/9279)) +- Fix plugin API fills and strokes array elements being read-only (by @RenzoMXD) [#8357](https://github.com/penpot/penpot/issues/8357) (PR: [#9161](https://github.com/penpot/penpot/pull/9161)) +- Fix "Show Guides" shortcut not working on German keyboards (by @RenzoMXD) [#8423](https://github.com/penpot/penpot/issues/8423) (PR: [#9209](https://github.com/penpot/penpot/pull/9209)) +- Fix token validation failing when a malformed token exists in the Component category [#9010](https://github.com/penpot/penpot/issues/9010) (PR: [#9025](https://github.com/penpot/penpot/pull/9025), [#9825](https://github.com/penpot/penpot/pull/9825)) +- Fix prototype interaction targets appearing in View Mode automatically when library component changes (by @jeffrey701) [#9049](https://github.com/penpot/penpot/issues/9049) (PR: [#9695](https://github.com/penpot/penpot/pull/9695)) +- Fix Docker frontend image missing CSS reference (by @NativeTeachingAidsB) [#9135](https://github.com/penpot/penpot/issues/9135) (PR: [#9840](https://github.com/penpot/penpot/pull/9840)) +- Fix MCP media upload error and SVG data URI image parsing (by @claytonlin1110) [#9164](https://github.com/penpot/penpot/issues/9164) (PR: [#9201](https://github.com/penpot/penpot/pull/9201)) +- Fix lost-update race on team features during concurrent file creation (by @JPette1783) [#9197](https://github.com/penpot/penpot/issues/9197) (PR: [#9198](https://github.com/penpot/penpot/pull/9198)) +- Fix get-profile RPC method silently masking DB errors as "Anonymous User" (by @jack-stormentswe) [#9253](https://github.com/penpot/penpot/issues/9253) (PR: [#9254](https://github.com/penpot/penpot/pull/9254)) +- Fix crash when creating or editing tokens named "white" or "black" [#9256](https://github.com/penpot/penpot/issues/9256) (PR: [#9034](https://github.com/penpot/penpot/pull/9034)) +- Fix conditional use-ctx hook violation in shape-wrapper (by @Dexterity104) [#9280](https://github.com/penpot/penpot/issues/9280) (PR: [#9281](https://github.com/penpot/penpot/pull/9281)) +- Make ShapeImageIds byte conversion fallible to prevent panics (by @Dexterity104) [#9282](https://github.com/penpot/penpot/issues/9282) (PR: [#9283](https://github.com/penpot/penpot/pull/9283)) +- Prevent viewers from overwriting file thumbnails (by @jony376) [#9284](https://github.com/penpot/penpot/issues/9284) (PR: [#9285](https://github.com/penpot/penpot/pull/9285)) +- Fix plugin API showing incorrect error messages for invalid operations (by @bitcompass) [#9417](https://github.com/penpot/penpot/issues/9417) (PR: [#9486](https://github.com/penpot/penpot/pull/9486)) +- Add inactivity timeout to SSE sessions to match Streamable HTTP sessions [#9432](https://github.com/penpot/penpot/issues/9432) (PR: [#9464](https://github.com/penpot/penpot/pull/9464)) +- Fix component variant switching behaving differently on two identical copies (by @MischaPanch) [#9498](https://github.com/penpot/penpot/issues/9498) (PR: [#9434](https://github.com/penpot/penpot/pull/9434)) +- Populate is-indirect flag on file libraries from relation graph (by @Dexterity104) [#9506](https://github.com/penpot/penpot/issues/9506) (PR: [#9289](https://github.com/penpot/penpot/pull/9289)) +- Add missing error message for invalid shadow token [#9583](https://github.com/penpot/penpot/issues/9583) (PR: [#9809](https://github.com/penpot/penpot/pull/9809)) +- Fix moving a component in a library triggering stale update notification in dependent files [#9629](https://github.com/penpot/penpot/issues/9629) (PR: [#9616](https://github.com/penpot/penpot/pull/9616)) +- Fix newly created token not visible when placed above existing tokens in the tree [#9711](https://github.com/penpot/penpot/issues/9711) (PR: [#9803](https://github.com/penpot/penpot/pull/9803)) +- Fix B(V) input label misalignment in HSB color picker [#9731](https://github.com/penpot/penpot/issues/9731) (PR: [#9793](https://github.com/penpot/penpot/pull/9793)) +- Fix text style name input appending font name instead of replacing it when edited [#9785](https://github.com/penpot/penpot/issues/9785) (PR: [#9784](https://github.com/penpot/penpot/pull/9784)) +- Fix shadow token creation not allowing empty blur or spread value [#9808](https://github.com/penpot/penpot/issues/9808) (PR: [#9809](https://github.com/penpot/penpot/pull/9809)) +- Fix thinner line in path when its stroke is deleted and added again [#9823](https://github.com/penpot/penpot/issues/9823) (PR: [#9836](https://github.com/penpot/penpot/pull/9836)) +- Fix layers panel perceivable lag when displaying changes [#9834](https://github.com/penpot/penpot/issues/9834) +- Fix settings form visual layout broken after recent contribution [#9882](https://github.com/penpot/penpot/issues/9882) (PR: [#9883](https://github.com/penpot/penpot/pull/9883)) +- Fix crash when duplicating shapes with fill/stroke properties [#9893](https://github.com/penpot/penpot/issues/9893) (PR: [#9647](https://github.com/penpot/penpot/pull/9647)) +- Fix S3 storage failing with IRSA/Web Identity Token credentials (by @jpc2350) [#9927](https://github.com/penpot/penpot/issues/9927) (PR: [#9928](https://github.com/penpot/penpot/pull/9928)) +- Fix onboarding template spinner stuck after failed template download (by @jeffrey701) [#9931](https://github.com/penpot/penpot/issues/9931) (PR: [#9504](https://github.com/penpot/penpot/pull/9504)) +- Fix stroke caps not working correctly when there are other nodes in the middle of a path [#9987](https://github.com/penpot/penpot/issues/9987) (PR: [#9989](https://github.com/penpot/penpot/pull/9989)) +- Fix missing three dots button for column and row edit menu in WebKit/Safari [#9993](https://github.com/penpot/penpot/issues/9993) (PR: [#9994](https://github.com/penpot/penpot/pull/9994)) +- Fix exported path with strokes being cut off in SVG file [#9995](https://github.com/penpot/penpot/issues/9995) (PR: [#9996](https://github.com/penpot/penpot/pull/9996)) +- Fix French Canada locale falling back to French translations instead of French Canadian (by @alexismo) [#10017](https://github.com/penpot/penpot/issues/10017) (PR: [#10027](https://github.com/penpot/penpot/pull/10027)) -## 2.16.0 (Unreleased) +## 2.16.0 ### :boom: Breaking changes & Deprecations @@ -90,7 +138,6 @@ - Duplicate token group [#9638](https://github.com/penpot/penpot/issues/9638) (PR: [#8886](https://github.com/penpot/penpot/pull/8886)) - Copy token name from contextual menu [#9639](https://github.com/penpot/penpot/issues/9639) (PR: [#8566](https://github.com/penpot/penpot/pull/8566)) - Add drag-to-change for numeric inputs in workspace sidebar (by @RenzoMXD) [#2466](https://github.com/penpot/penpot/issues/2466) (PR: [#8536](https://github.com/penpot/penpot/pull/8536)) -- Add CSS linter [#9636](https://github.com/penpot/penpot/issues/9636) (PR: [#8592](https://github.com/penpot/penpot/pull/8592)) - Add per-group add button for typographies (by @eureka0928) [#5275](https://github.com/penpot/penpot/issues/5275) (PR: [#8895](https://github.com/penpot/penpot/pull/8895)) - Add Find & Replace for text content and layer names (by @statxc) [#7108](https://github.com/penpot/penpot/issues/7108) (PR: [#8899](https://github.com/penpot/penpot/pull/8899), [#9687](https://github.com/penpot/penpot/pull/9687)) - Use page name for multi-export ZIP/PDF downloads (by @Dexterity104) [#8773](https://github.com/penpot/penpot/issues/8773) (PR: [#8874](https://github.com/penpot/penpot/pull/8874)) @@ -121,10 +168,9 @@ - Preserve Inkscape labels when pasting SVGs (by @jeffrey701) [#7869](https://github.com/penpot/penpot/issues/7869) (PR: [#9252](https://github.com/penpot/penpot/pull/9252)) - Add Alt+click to expand layer subtree (by @MilosM348) [#7736](https://github.com/penpot/penpot/issues/7736) (PR: [#9179](https://github.com/penpot/penpot/pull/9179)) - Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068)) -- Clarify self-hosted OIDC configuration for containerized (by @sancfc) [#9764](https://github.com/penpot/penpot/issues/9764) (PR: [#9758](https://github.com/penpot/penpot/pull/9758)) -- Update User Guide with 2.16 features (by @myfunnyandy) [#9767](https://github.com/penpot/penpot/issues/9767) (PR: [#9768](https://github.com/penpot/penpot/pull/9768)) - Improve file validation performance and fix orphan shape detection [#9790](https://github.com/penpot/penpot/issues/9790) (PR: [#9789](https://github.com/penpot/penpot/pull/9789)) - Add v2.16 release notes (What's new modal) [#9945](https://github.com/penpot/penpot/issues/9945) (PR: [#9940](https://github.com/penpot/penpot/pull/9940)) +- Enable multi-instance horizontal scaling for MCP server [#10000](https://github.com/penpot/penpot/issues/10000) (PR: [#10013](https://github.com/penpot/penpot/pull/10013)) ### :bug: Bugs fixed @@ -186,7 +232,6 @@ - Fix `:heigth` typo in clipboard frame-same-size? (by @iot2edge) [#9249](https://github.com/penpot/penpot/issues/9249) (PR: [#9250](https://github.com/penpot/penpot/pull/9250)) - Fix Settings Update button enabled state (by @moorsecopers99) [#9090](https://github.com/penpot/penpot/issues/9090) (PR: [#9091](https://github.com/penpot/penpot/pull/9091)) - Fix library updates reappearing after reload [#9326](https://github.com/penpot/penpot/issues/9326) (PR: [#9563](https://github.com/penpot/penpot/pull/9563)) -- Fix dependency libraries visible after unlinking main library [#9331](https://github.com/penpot/penpot/issues/9331) (PR: [#9511](https://github.com/penpot/penpot/pull/9511)) - Fix internal error on margins [#9309](https://github.com/penpot/penpot/issues/9309) (PR: [#9311](https://github.com/penpot/penpot/pull/9311)) - Remove drag-to-change when token applied on numeric input [#9313](https://github.com/penpot/penpot/issues/9313) (PR: [#9314](https://github.com/penpot/penpot/pull/9314)) - Fix extra input on canvas background [#9359](https://github.com/penpot/penpot/issues/9359) (PR: [#9360](https://github.com/penpot/penpot/pull/9360)) @@ -194,20 +239,16 @@ - Fix several color picker issues [#9556](https://github.com/penpot/penpot/issues/9556) (PR: [#9558](https://github.com/penpot/penpot/pull/9558)) - Fix asset icon broken on Asset tab [#9587](https://github.com/penpot/penpot/issues/9587) (PR: [#9612](https://github.com/penpot/penpot/pull/9612)) - Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549)) -- Fix editing a legacy text element silently detaches its color token [#9255](https://github.com/penpot/penpot/issues/9255) (PR: [#9525](https://github.com/penpot/penpot/pull/9525)) -- Fix token application to grid paddings [#9494](https://github.com/penpot/penpot/issues/9494) (PR: [#9630](https://github.com/penpot/penpot/pull/9630)) -- Fix file crashing when switching a variant [#9259](https://github.com/penpot/penpot/issues/9259) (PR: [#9147](https://github.com/penpot/penpot/pull/9147)) -- Fix set activation after renaming [#9329](https://github.com/penpot/penpot/issues/9329) (PR: [#9545](https://github.com/penpot/penpot/pull/9545)) -- Fix font selection position hiding available fonts [#9489](https://github.com/penpot/penpot/issues/9489) (PR: [#9499](https://github.com/penpot/penpot/pull/9499)) -- Fix numeric input changes not saved when clicking on viewport [#9491](https://github.com/penpot/penpot/issues/9491) (PR: [#9548](https://github.com/penpot/penpot/pull/9548)) -- Fix resize cursor appearing on login and register buttons [#9505](https://github.com/penpot/penpot/issues/9505) (PR: [#9590](https://github.com/penpot/penpot/pull/9590)) -- Fix version restore restoring first previewed version instead of selected one [#9588](https://github.com/penpot/penpot/issues/9588) (PR: [#9626](https://github.com/penpot/penpot/pull/9626)) -- Fix incorrect error message when applying tokens while editing text [#9620](https://github.com/penpot/penpot/issues/9620) (PR: [#9708](https://github.com/penpot/penpot/pull/9708)) - Fix standalone tokens ordering separated from token groups [#9733](https://github.com/penpot/penpot/issues/9733) (PR: [#9736](https://github.com/penpot/penpot/pull/9736)) - Fix delete invitation modal readability in light theme [#9737](https://github.com/penpot/penpot/issues/9737) (PR: [#9747](https://github.com/penpot/penpot/pull/9747)) - Fix team invitation not automatically accepted after account validation [#9776](https://github.com/penpot/penpot/issues/9776) (PR: [#9782](https://github.com/penpot/penpot/pull/9782)) -- Fix design tokens vanishing from the sidebar when a token name collides with a token-group prefix from another active set (e.g. `a` in one set and `a.b` in another); the colliding token is now kept and rendered as a broken pill [Github #9584](https://github.com/penpot/penpot/issues/9584) - Fix Plugin API addRulerGuide creating guides on page instead of board (by @girafic) [#8225](https://github.com/penpot/penpot/issues/8225) (PR: [#8632](https://github.com/penpot/penpot/pull/8632)) +- Fix text editor not swapping correctly when enabling/disabling WebGL [#10015](https://github.com/penpot/penpot/issues/10015) +- Fix WebGL renderer focus mode leaving artefacts [#10061](https://github.com/penpot/penpot/issues/10061) (PR: [#10091](https://github.com/penpot/penpot/pull/10091)) +- Fix double click on text selecting underlying element when WebGL render is enabled [#10080](https://github.com/penpot/penpot/issues/10080) (PR: [#10123](https://github.com/penpot/penpot/pull/10123)) +- Fix publishing or unpublishing file as library failing with unexpected state found error [#10094](https://github.com/penpot/penpot/issues/10094) (PR: [#10093](https://github.com/penpot/penpot/pull/10093)) +- Fix team invitation failing when email address contains consecutive dots in domain [#10097](https://github.com/penpot/penpot/issues/10097) (PR: [#10096](https://github.com/penpot/penpot/pull/10096)) +- Add detailed error messages for unspecified import errors [#9759](https://github.com/penpot/penpot/issues/9759) (PR: [#9886](https://github.com/penpot/penpot/pull/9886)) ## 2.15.4 diff --git a/SECURITY.md b/SECURITY.md index 77896640b3..3f2bd81af7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,8 +5,8 @@ We take the security of this project seriously. If you have discovered a security vulnerability, please do **not** open a public issue. -Please report vulnerabilities via email to: **[support@penpot.app]** - +Please report vulnerabilities through the [GitHub Security Advisories](https://github.com/penpot/penpot/security/advisories +) feature in the Penpot repository. ### What to include: diff --git a/THANKYOU.md b/THANKYOU.md index 8c075112b3..8651946161 100644 --- a/THANKYOU.md +++ b/THANKYOU.md @@ -7,6 +7,9 @@ list. ## Security +* Noob Researcher +* Kirubakaran N +* Alisher (@7megaumka7) * Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD) * [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/) * Vaibhav Shukla diff --git a/backend/deps.edn b/backend/deps.edn index 1025a0760b..286b52dc3f 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -4,9 +4,9 @@ :deps {penpot/common {:local/root "../common"} org.clojure/clojure {:mvn/version "1.12.5"} - org.clojure/tools.namespace {:mvn/version "1.5.0"} + org.clojure/tools.namespace {:mvn/version "1.5.1"} - com.github.luben/zstd-jni {:mvn/version "1.5.7-4"} + com.github.luben/zstd-jni {:mvn/version "1.5.7-10"} io.prometheus/simpleclient {:mvn/version "0.16.0"} io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"} @@ -17,7 +17,7 @@ io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"} - io.lettuce/lettuce-core {:mvn/version "7.5.1.RELEASE"} + io.lettuce/lettuce-core {:mvn/version "7.6.0.RELEASE"} ;; Minimal dependencies required by lettuce, we need to include them ;; explicitly because clojure dependency management does not support ;; yet the BOM format. @@ -25,7 +25,7 @@ io.micrometer/micrometer-observation {:mvn/version "1.14.2"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - com.google.guava/guava {:mvn/version "33.4.8-jre"} + com.google.guava/guava {:mvn/version "33.6.0-jre"} funcool/yetti {:git/tag "v11.10" @@ -34,13 +34,13 @@ :exclusions [org.slf4j/slf4j-api]} com.github.seancorfield/next.jdbc - {:mvn/version "1.3.1093"} + {:mvn/version "1.3.1108"} - metosin/reitit-core {:mvn/version "0.9.1"} + metosin/reitit-core {:mvn/version "0.10.1"} nrepl/nrepl {:mvn/version "1.7.0"} org.postgresql/postgresql {:mvn/version "42.7.11"} - org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"} + org.xerial/sqlite-jdbc {:mvn/version "3.53.2.0"} com.zaxxer/HikariCP {:mvn/version "7.0.2"} @@ -51,7 +51,7 @@ com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"} - org.jsoup/jsoup {:mvn/version "1.21.2"} + org.jsoup/jsoup {:mvn/version "1.22.2"} org.im4java/im4java {:git/tag "1.4.0-penpot-2" :git/sha "e2b3e16" @@ -63,18 +63,18 @@ org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} dawran6/emoji {:mvn/version "0.2.0"} - markdown-clj/markdown-clj {:mvn/version "1.12.4"} + markdown-clj/markdown-clj {:mvn/version "1.12.8"} ;; Pretty Print specs pretty-spec/pretty-spec {:mvn/version "0.1.4"} - software.amazon.awssdk/s3 {:mvn/version "2.44.4"} - software.amazon.awssdk/sts {:mvn/version "2.44.4"}} + software.amazon.awssdk/s3 {:mvn/version "2.46.7"} + software.amazon.awssdk/sts {:mvn/version "2.46.7"}} :paths ["src" "resources" "target/classes"] :aliases {:dev {:extra-deps - {com.bhauman/rebel-readline {:mvn/version "0.1.5"} + {com.bhauman/rebel-readline {:mvn/version "0.1.7"} clojure-humanize/clojure-humanize {:mvn/version "0.2.2"} org.clojure/data.csv {:mvn/version "1.1.1"} com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"} @@ -83,7 +83,7 @@ :build {:extra-deps - {io.github.clojure/tools.build {:mvn/version "0.10.10"}} + {io.github.clojure/tools.build {:mvn/version "0.10.14"}} :ns-default build} :test diff --git a/backend/package.json b/backend/package.json index ca54303330..e6eb030f17 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 0fb6cc67a2..959e96c3e1 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -545,6 +545,12 @@ ::audit/context {:action "email-verification"} ::audit/profile-id (:id profile)}))))) + ;; When email verification is disabled and an inactive profile already + ;; exists, reject the registration — the email is already taken. + (not (contains? cf/flags :email-verification)) + (ex/raise :type :validation + :code :email-already-exists) + :else (let [elapsed? (elapsed-verify-threshold? profile) reports? (eml/has-reports? conn (:email profile)) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index a05ef28e78..e8a4bc32b0 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -974,6 +974,12 @@ {:id id}) file) + (= (:is-shared file) (:is-shared params)) + ;; File is already in the desired state (idempotent); + ;; this can happen when the frontend sends a duplicate + ;; request due to optimistic updates or race conditions. + file + :else (ex/raise :type :validation :code :invalid-shared-state diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index 5c23d7ab31..130d9a86be 100644 --- a/backend/src/app/rpc/commands/files_thumbnails.clj +++ b/backend/src/app/rpc/commands/files_thumbnails.clj @@ -311,11 +311,30 @@ :object-id object-id :tag tag}))) +(defn- delete-file-object-thumbnails! + "Soft-deletes multiple object thumbnails in a single UPDATE statement + with RETURNING, then touches all returned media objects." + [{:keys [::db/conn ::sto/storage]} object-ids] + (let [ids (db/create-array conn "text" (seq object-ids)) + sql (str/concat + "UPDATE file_tagged_object_thumbnail" + " SET deleted_at = now()" + " WHERE object_id = ANY(?)" + " AND deleted_at IS NULL" + " RETURNING media_id") + rows (db/exec! conn [sql ids])] + (doseq [{:keys [media-id]} rows] + (sto/touch-object! storage media-id)))) + (def ^:private schema:delete-file-object-thumbnail [:map {:title "delete-file-object-thumbnail"} [:file-id ::sm/uuid] [:object-id [:string {:max 250}]]]) +(def ^:private schema:delete-file-object-thumbnails + [:map {:title "delete-file-object-thumbnails"} + [:object-ids [:vector {:max 200} [:string {:max 250}]]]]) + (sv/defmethod ::delete-file-object-thumbnail {::doc/added "1.19" ::doc/module :files @@ -329,6 +348,30 @@ (delete-file-object-thumbnail! file-id object-id)) nil))) +(sv/defmethod ::delete-file-object-thumbnails + {::doc/added "1.19" + ::doc/module :files + ::climit/id [[:file-thumbnail-ops/by-profile ::rpc/profile-id] + [:file-thumbnail-ops/global]] + ::sm/params schema:delete-file-object-thumbnails + ::audit/skip true} + [cfg {:keys [::rpc/profile-id object-ids]}] + (when (seq object-ids) + ;; Extract unique file-ids from object-ids for permission checks + (let [file-ids (->> object-ids + (map thc/get-file-id) + (into #{}))] + ;; Check permissions for each unique file using a single connection + (db/run! cfg (fn [{:keys [::db/conn]}] + (doseq [file-id file-ids] + (files/check-edition-permissions! conn profile-id file-id)))) + ;; Delete all matching thumbnails in one transaction + (db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}] + (-> cfg + (update ::sto/storage sto/configure conn) + (delete-file-object-thumbnails! object-ids)) + nil))))) + ;; --- MUTATION COMMAND: create-file-thumbnail (defn- create-file-thumbnail diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 221896f385..ba1af4a362 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -89,6 +89,12 @@ email)] email)) +(defn- with-nitrate-licence + [profile cfg] + (if (contains? cf/flags :nitrate) + (nitrate/add-nitrate-licence-to-profile cfg profile) + profile)) + ;; --- QUERY: Get profile (own) @@ -106,9 +112,7 @@ (let [profile (-> (get-profile pool profile-id) (strip-private-attrs) (update :props filter-props))] - (if (contains? cf/flags :nitrate) - (nitrate/add-nitrate-licence-to-profile cfg profile) - profile)) + (with-nitrate-licence profile cfg)) (catch Throwable cause (if (= :not-found (-> cause ex-data :type)) @@ -137,7 +141,7 @@ ::sm/params schema:update-profile ::sm/result schema:profile ::db/transaction true} - [{:keys [::db/conn]} {:keys [::rpc/profile-id fullname lang theme] :as params}] + [{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id fullname lang theme] :as params}] ;; NOTE: we need to retrieve the profile independently if we use ;; it or not for explicit locking and avoid concurrent updates of ;; the same row/object. @@ -158,6 +162,7 @@ (-> profile (strip-private-attrs) (d/without-nils) + (with-nitrate-licence cfg) (rph/with-meta {::audit/props (audit/profile->props profile)})))) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 7e011c53a8..247b27a699 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -62,7 +62,7 @@ (def default {:database-uri "postgresql://postgres/penpot_test" - :redis-uri "redis://redis/1" + :redis-uri "redis://valkey/1" :auto-file-snapshot-every 1 :file-data-backend "db"}) diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index 24bb45cb80..5460b4143f 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -830,6 +830,49 @@ (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :not-found)))) +(t/deftest set-file-shared-idempotent + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:project-id (:default-project-id profile) + :profile-id (:id profile)})] + + ;; Share the file + (let [data {::th/type :set-file-shared + ::rpc/profile-id (:id profile) + :id (:id file) + :is-shared true} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (true? (-> out :result :is-shared)))) + + ;; Calling set-file-shared with is-shared=true again should be a + ;; no-op success (idempotent), not an error. + (let [data {::th/type :set-file-shared + ::rpc/profile-id (:id profile) + :id (:id file) + :is-shared true} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (true? (-> out :result :is-shared)))) + + ;; Unshare the file + (let [data {::th/type :set-file-shared + ::rpc/profile-id (:id profile) + :id (:id file) + :is-shared false} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (false? (-> out :result :is-shared)))) + + ;; Calling set-file-shared with is-shared=false again should also + ;; be a no-op success (idempotent). + (let [data {::th/type :set-file-shared + ::rpc/profile-id (:id profile) + :id (:id file) + :is-shared false} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (false? (-> out :result :is-shared)))))) + (t/deftest permissions-checks-link-to-library-1 (let [profile1 (th/create-profile* 1) profile2 (th/create-profile* 2) diff --git a/backend/test/backend_tests/rpc_file_thumbnails_test.clj b/backend/test/backend_tests/rpc_file_thumbnails_test.clj index 72cb355636..4fb4ab12e1 100644 --- a/backend/test/backend_tests/rpc_file_thumbnails_test.clj +++ b/backend/test/backend_tests/rpc_file_thumbnails_test.clj @@ -380,3 +380,538 @@ (t/is (nil? (:error out))) (t/is (map? (:result out)))))) +;; --- delete-file-object-thumbnails (batch) + +(t/deftest delete-file-object-thumbnails-basic + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid3 (thc/fmt-object-id (:id file) page-id (uuid/random) "component")] + + ;; Create three thumbnails + (doseq [oid [oid1 oid2 oid3]] + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out))))) + + ;; Verify all three exist and are not soft-deleted + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false + :order-by [[:created-at :asc]]})] + (t/is (= 3 (count rows))) + (doseq [row rows] + (t/is (nil? (:deleted-at row))))) + + ;; Batch delete all three + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1 oid2 oid3]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify all three are now soft-deleted + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false + :order-by [[:created-at :asc]]})] + (t/is (= 3 (count rows))) + (doseq [row rows] + (t/is (some? (:deleted-at row))))))) + +(t/deftest delete-file-object-thumbnails-empty + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids []} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out))))) + +(t/deftest delete-file-object-thumbnails-non-existent + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Batch delete non-existent object-ids (no thumbnails were created) + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1 oid2]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))))) + +(t/deftest delete-file-object-thumbnails-mixed-exists + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid3 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Create only one thumbnail + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid1 + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; Batch delete mix of existing and non-existing + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1 oid2 oid3]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify oid1 is soft-deleted, others don't exist + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows))) + (t/is (= oid1 (:object-id (first rows)))) + (t/is (some? (:deleted-at (first rows))))))) + +(t/deftest delete-file-object-thumbnails-already-deleted + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Create a thumbnail + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; First batch delete + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Second batch delete (idempotent — no rows match deleted_at IS NULL) + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify still 1 row, still soft-deleted, not duplicated + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows))) + (t/is (= oid (:object-id (first rows)))) + (t/is (some? (:deleted-at (first rows))))))) + +(t/deftest delete-file-object-thumbnails-unauthorized + (let [profile1 (th/create-profile* 1) + profile2 (th/create-profile* 2) + file (th/create-file* 1 {:profile-id (:id profile1) + :project-id (:default-project-id profile1) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; profile1 creates a thumbnail on their file + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile1) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; profile2 tries to batch delete thumbnails from profile1's file + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile2) + :object-ids [oid]} + out (th/command! data)] + (t/is (some? (:error out))) + (t/is (th/ex-info? (:error out))) + (t/is (= :not-found (th/ex-type (:error out))))) + + ;; Verify the thumbnail is NOT deleted + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows))) + (t/is (nil? (:deleted-at (first rows))))))) + +(t/deftest delete-file-object-thumbnails-cross-file + (let [profile (th/create-profile* 1) + file1 (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + file2 (th/create-file* 2 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page1-id (first (get-in file1 [:data :pages])) + page2-id (first (get-in file2 [:data :pages])) + oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")] + + ;; Create thumbnails on both files + (doseq [[oid fid] [[oid1 (:id file1)] [oid2 (:id file2)]]] + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id fid + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out))))) + + ;; Batch delete from both files in one call + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1 oid2]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify both are soft-deleted + (let [rows1 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file1)} + {::db/remove-deleted false}) + rows2 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file2)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows1))) + (t/is (some? (:deleted-at (first rows1)))) + (t/is (= 1 (count rows2))) + (t/is (some? (:deleted-at (first rows2))))))) + +(t/deftest delete-file-object-thumbnails-cross-file-unauthorized + (let [profile1 (th/create-profile* 1) + profile2 (th/create-profile* 2) + file1 (th/create-file* 1 {:profile-id (:id profile1) + :project-id (:default-project-id profile1) + :is-shared false}) + file2 (th/create-file* 2 {:profile-id (:id profile2) + :project-id (:default-project-id profile2) + :is-shared false}) + page1-id (first (get-in file1 [:data :pages])) + page2-id (first (get-in file2 [:data :pages])) + oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")] + + ;; Create thumbnails on both files (by their respective owners) + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile1) + :file-id (:id file1) + :object-id oid1 + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile2) + :file-id (:id file2) + :object-id oid2 + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; profile1 tries to batch delete thumbnails from both files + ;; (profile1 does NOT have access to file2) + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile1) + :object-ids [oid1 oid2]} + out (th/command! data)] + (t/is (some? (:error out))) + (t/is (th/ex-info? (:error out))) + (t/is (= :not-found (th/ex-type (:error out))))) + + ;; Verify NEITHER thumbnail was deleted (all-or-nothing) + (let [rows1 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file1)} + {::db/remove-deleted false}) + rows2 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file2)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows1))) + (t/is (nil? (:deleted-at (first rows1)))) + (t/is (= 1 (count rows2))) + (t/is (nil? (:deleted-at (first rows2))))))) + +(t/deftest delete-file-object-thumbnails-media-touch + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Create two thumbnails + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid1 + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid2 + :media {:filename "sample.jpg" + :size 312043 + :path (th/tempfile "backend_tests/test_files/sample.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; Get media-ids for both thumbnails + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {:order-by [[:created-at :asc]]}) + mid1 (:media-id (first rows)) + mid2 (:media-id (second rows))] + + ;; Verify storage objects exist (they are created with touched-at already set) + (t/is (some? (th/db-get :storage-object {:id mid1}))) + (t/is (some? (th/db-get :storage-object {:id mid2}))) + + ;; Batch delete both thumbnails + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1 oid2]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; After soft-delete, storage objects should STILL exist + ;; (they are only garbage-collected later by storage-gc-touched task) + (t/is (some? (th/db-get :storage-object {:id mid1}))) + (t/is (some? (th/db-get :storage-object {:id mid2})))))) + +(t/deftest delete-file-object-thumbnails-max-batch + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + cnt 200 + oids (vec (repeatedly cnt + #(thc/fmt-object-id (:id file) page-id + (uuid/random) "frame")))] + + ;; Create 200 thumbnails + (doseq [oid oids] + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out))))) + + ;; Verify all 200 exist + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= cnt (count rows)))) + + ;; Batch delete all 200 in one call + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids oids} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify all 200 are now soft-deleted + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= cnt (count rows))) + (doseq [row rows] + (t/is (some? (:deleted-at row))))))) + +(t/deftest delete-file-object-thumbnails-single + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Create a single thumbnail + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; Batch delete just one + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify it's soft-deleted + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows))) + (t/is (some? (:deleted-at (first rows))))))) + +(t/deftest delete-file-object-thumbnails-same-object-twice-in-batch + (let [profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page-id (first (get-in file [:data :pages])) + oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")] + + ;; Create one thumbnail + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + ;; Batch delete with the same object-id listed twice + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid oid]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify it's soft-deleted (only one row) + (let [rows (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows))) + (t/is (some? (:deleted-at (first rows))))))) + +(t/deftest delete-file-object-thumbnails-keeps-other-files-intact + (let [profile (th/create-profile* 1) + file1 (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + file2 (th/create-file* 2 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + page1-id (first (get-in file1 [:data :pages])) + page2-id (first (get-in file2 [:data :pages])) + oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame") + oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")] + + ;; Create thumbnails on both files + (doseq [[oid fid] [[oid1 (:id file1)] [oid2 (:id file2)]]] + (let [data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id fid + :object-id oid + :media {:filename "sample.jpg" + :size 7923 + :path (th/tempfile "backend_tests/test_files/sample2.jpg") + :mtype "image/jpeg"}} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out))))) + + ;; Delete only thumbnail from file1 + (let [data {::th/type :delete-file-object-thumbnails + ::rpc/profile-id (:id profile) + :object-ids [oid1]} + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + ;; Verify file1's thumbnail is deleted, file2's is not + (let [rows1 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file1)} + {::db/remove-deleted false}) + rows2 (th/db-query :file-tagged-object-thumbnail + {:file-id (:id file2)} + {::db/remove-deleted false})] + (t/is (= 1 (count rows1))) + (t/is (some? (:deleted-at (first rows1)))) + (t/is (= 1 (count rows2))) + (t/is (nil? (:deleted-at (first rows2))))))) + diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index b1d1b1324b..7adc12d68e 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -12,6 +12,7 @@ [app.db :as db] [app.email.blacklist :as email.blacklist] [app.email.whitelist :as email.whitelist] + [app.nitrate :as nitrate] [app.rpc :as-alias rpc] [app.rpc.commands.profile :as profile] [app.tokens :as tokens] @@ -90,17 +91,26 @@ (t/is (not (contains? result :password)))))) (t/testing "update profile" - (let [data (assoc profile - ::th/type :update-profile - ::rpc/profile-id (:id profile) - :fullname "Full Name" - :lang "en" - :theme "dark") - out (th/command! data)] + (with-redefs [app.config/flags #{:nitrate}] + (with-redefs [nitrate/add-nitrate-licence-to-profile + (fn [_ profile] + (assoc profile :subscription {:plan :pro}))] + (let [data (assoc profile + ::th/type :update-profile + ::rpc/profile-id (:id profile) + :fullname "Full Name" + :lang "en" + :theme "dark") + out (th/command! data)] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (map? (:result out))))) + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (map? (:result out))) + (t/is (= "Full Name" (get-in out [:result :fullname]))) + (t/is (= "en" (get-in out [:result :lang]))) + (t/is (= "dark" (get-in out [:result :theme]))) + (t/is (= {:plan :pro} + (:subscription (:result out)))))))) (t/testing "query profile after update" (let [data {::th/type :get-profile @@ -526,6 +536,65 @@ (t/is (nil? (:error out))) (t/is (= 0 (:call-count @mock)))))))) +(t/deftest prepare-register-and-register-profile-disable-email-verification + ;; When disable-email-verification is set and the profile is inactive + ;; (e.g. created before the flag was set), re-registering should be + ;; rejected with :email-already-exists. + (with-mocks [mock {:target 'app.email/send! :return nil}] + (with-redefs [app.config/flags #{:registration :login-with-password}] + (let [current-token (atom nil)] + ;; PREPARE REGISTER: first attempt (no profile exists yet) + (let [data {::th/type :prepare-register-profile + :email "hello@example.com" + :fullname "foobar" + :password "foobar"} + out (th/command! data) + token (get-in out [:result :token])] + (t/is (th/success? out)) + (reset! current-token token)) + + ;; DO REGISTRATION: creates active profile (email-verification disabled) + (let [data {::th/type :register-profile + :token @current-token} + out (th/command! data) + mdata (-> out :result meta)] + (t/is (nil? (:error out))) + ;; No verification email sent + (t/is (= 0 (:call-count @mock))) + ;; Session is minted + (t/is (seq (:app.rpc/response-transform-fns mdata)))) + + ;; Force the profile back to inactive to simulate the case where it was + ;; created before disable-email-verification was set + (th/db-update! :profile + {:is-active false} + {:email "hello@example.com"}) + + (th/reset-mock! mock) + + ;; PREPARE REGISTER: second attempt (inactive profile exists) + (let [data {::th/type :prepare-register-profile + :email "hello@example.com" + :fullname "foobar" + :password "foobar"} + out (th/command! data) + token (get-in out [:result :token])] + (t/is (th/success? out)) + (reset! current-token token)) + + ;; DO REGISTRATION: second attempt should be rejected + (let [data {::th/type :register-profile + :token @current-token} + out (th/command! data) + error (:error out)] + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :validation)) + (t/is (th/ex-of-code? error :email-already-exists)) + ;; No email sent, profile remains inactive + (t/is (= 0 (:call-count @mock))) + (let [profile (th/db-get :profile {:email "hello@example.com"})] + (t/is (false? (:is-active profile))))))))) + (t/deftest prepare-and-register-with-invitation-and-enabled-registration-1 ;; With email-verification ENABLED (the default), a brand-new ;; profile created via the invitation flow is NOT active yet, so diff --git a/common/deps.edn b/common/deps.edn index 74fcef0f28..2f7b2a1b76 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -6,7 +6,7 @@ org.clojure/data.fressian {:mvn/version "1.1.1"} org.clojure/clojurescript {:mvn/version "1.12.42"} - org.apache.commons/commons-pool2 {:mvn/version "2.12.1"} + org.apache.commons/commons-pool2 {:mvn/version "2.13.1"} ;; Logging org.apache.logging.log4j/log4j-api {:mvn/version "2.26.0"} @@ -15,12 +15,12 @@ org.apache.logging.log4j/log4j-jul {:mvn/version "2.26.0"} org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.26.0"} org.slf4j/slf4j-api {:mvn/version "2.0.18"} - pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.41"} + pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.42"} - selmer/selmer {:mvn/version "1.13.1"} + selmer/selmer {:mvn/version "1.13.4"} criterium/criterium {:mvn/version "0.4.6"} - metosin/jsonista {:mvn/version "0.3.13"} + metosin/jsonista {:mvn/version "1.0.0"} metosin/malli {:mvn/version "0.19.1"} expound/expound {:mvn/version "0.9.0"} @@ -55,7 +55,7 @@ :aliases {:dev {:extra-deps - {org.clojure/tools.namespace {:mvn/version "1.5.0"} + {org.clojure/tools.namespace {:mvn/version "1.5.1"} thheller/shadow-cljs {:mvn/version "3.2.0"} com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"} com.bhauman/rebel-readline {:mvn/version "0.1.5"} @@ -65,7 +65,7 @@ :build {:extra-deps - {io.github.clojure/tools.build {:mvn/version "0.10.10"}} + {io.github.clojure/tools.build {:mvn/version "0.10.14"}} :ns-default build} :test diff --git a/common/package.json b/common/package.json index cd9fff30b8..d2129bb7ca 100644 --- a/common/package.json +++ b/common/package.json @@ -4,21 +4,21 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "type": "module", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" }, "devDependencies": { - "concurrently": "^9.1.2", - "nodemon": "^3.1.10", - "prettier": "3.5.3", + "concurrently": "^10.0.3", + "nodemon": "^3.1.14", + "prettier": "3.8.4", "source-map-support": "^0.5.21", - "ws": "^8.18.2" + "ws": "^8.21.0" }, "dependencies": { - "date-fns": "^4.1.0" + "date-fns": "^4.4.0" }, "scripts": { "lint:clj": "clj-kondo --parallel=true --lint src/", diff --git a/common/pnpm-lock.yaml b/common/pnpm-lock.yaml index 7f63f16b3c..10887c155b 100644 --- a/common/pnpm-lock.yaml +++ b/common/pnpm-lock.yaml @@ -9,48 +9,50 @@ importers: .: dependencies: date-fns: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.4.0 + version: 4.4.0 devDependencies: concurrently: - specifier: ^9.1.2 - version: 9.2.1 + specifier: ^10.0.3 + version: 10.0.3 nodemon: - specifier: ^3.1.10 - version: 3.1.11 + specifier: ^3.1.14 + version: 3.1.14 prettier: - specifier: 3.5.3 - version: 3.5.3 + specifier: 3.8.4 + version: 3.8.4 source-map-support: specifier: ^0.5.21 version: 0.5.21 ws: - specifier: ^8.18.2 - version: 8.18.3 + specifier: ^8.21.0 + version: 8.21.0 packages: - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -59,35 +61,25 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} - engines: {node: '>=18'} + concurrently@10.0.3: + resolution: {integrity: sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==} + engines: {node: '>=22'} hasBin: true - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -98,8 +90,8 @@ packages: supports-color: optional: true - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} @@ -118,6 +110,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -126,10 +122,6 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} @@ -141,10 +133,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -153,14 +141,15 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nodemon@3.1.11: - resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==} + nodemon@3.1.14: + resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} engines: {node: '>=10'} hasBin: true @@ -168,12 +157,12 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} engines: {node: '>=14'} hasBin: true @@ -184,20 +173,16 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} engines: {node: '>=10'} hasBin: true - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} simple-update-notifier@2.0.0: @@ -211,26 +196,22 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -249,12 +230,12 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -269,35 +250,32 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} snapshots: - ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 + ansi-styles@6.2.3: {} anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} binary-extensions@2.3.0: {} - brace-expansion@1.1.12: + brace-expansion@5.0.6: dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -305,10 +283,7 @@ snapshots: buffer-from@1.1.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + chalk@5.6.2: {} chokidar@3.6.0: dependencies: @@ -322,30 +297,22 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - cliui@8.0.1: + cliui@9.0.1: dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 - color-convert@2.0.1: + concurrently@10.0.3: dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - - concurrently@9.2.1: - dependencies: - chalk: 4.1.2 + chalk: 5.6.2 rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 + shell-quote: 1.8.4 + supports-color: 10.2.2 tree-kill: 1.2.2 - yargs: 17.7.2 + yargs: 18.0.0 - date-fns@4.1.0: {} + date-fns@4.4.0: {} debug@4.4.3(supports-color@5.5.0): dependencies: @@ -353,7 +320,7 @@ snapshots: optionalDependencies: supports-color: 5.5.0 - emoji-regex@8.0.0: {} + emoji-regex@10.6.0: {} escalade@3.2.0: {} @@ -366,14 +333,14 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 has-flag@3.0.0: {} - has-flag@4.0.0: {} - ignore-by-default@1.0.1: {} is-binary-path@2.1.0: @@ -382,28 +349,26 @@ snapshots: is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-number@7.0.0: {} - minimatch@3.1.2: + minimatch@10.2.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 5.0.6 ms@2.1.3: {} - nodemon@3.1.11: + nodemon@3.1.14: dependencies: chokidar: 3.6.0 debug: 4.4.3(supports-color@5.5.0) ignore-by-default: 1.0.1 - minimatch: 3.1.2 + minimatch: 10.2.5 pstree.remy: 1.1.8 - semver: 7.7.3 + semver: 7.8.4 simple-update-notifier: 2.0.0 supports-color: 5.5.0 touch: 3.1.1 @@ -411,29 +376,27 @@ snapshots: normalize-path@3.0.0: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - prettier@3.5.3: {} + prettier@3.8.4: {} pstree.remy@1.1.8: {} readdirp@3.6.0: dependencies: - picomatch: 2.3.1 - - require-directory@2.1.1: {} + picomatch: 2.3.2 rxjs@7.8.2: dependencies: tslib: 2.8.1 - semver@7.7.3: {} + semver@7.8.4: {} - shell-quote@1.8.3: {} + shell-quote@1.8.4: {} simple-update-notifier@2.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.4 source-map-support@0.5.21: dependencies: @@ -442,28 +405,22 @@ snapshots: source-map@0.6.1: {} - string-width@4.2.3: + string-width@7.2.0: dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 - strip-ansi@6.0.1: + strip-ansi@7.2.0: dependencies: - ansi-regex: 5.0.1 + ansi-regex: 6.2.2 + + supports-color@10.2.2: {} supports-color@5.5.0: dependencies: has-flag: 3.0.0 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -476,24 +433,23 @@ snapshots: undefsafe@2.0.5: {} - wrap-ansi@7.0.0: + wrap-ansi@9.0.2: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 - ws@8.18.3: {} + ws@8.21.0: {} y18n@5.0.8: {} - yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} - yargs@17.7.2: + yargs@18.0.0: dependencies: - cliui: 8.0.1 + cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 + string-width: 7.2.0 y18n: 5.0.8 - yargs-parser: 21.1.1 + yargs-parser: 22.0.0 diff --git a/common/pnpm-workspace.yaml b/common/pnpm-workspace.yaml index e69de29bb2..a1c031fc33 100644 --- a/common/pnpm-workspace.yaml +++ b/common/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/common/src/app/common/encoding_impl.js b/common/src/app/common/encoding_impl.js index 5baa369e88..10cd3d1263 100644 --- a/common/src/app/common/encoding_impl.js +++ b/common/src/app/common/encoding_impl.js @@ -159,7 +159,7 @@ goog.scope(function () { it1--, i++ ) { carry += (256 * b58[it1]) >>> 0; - b58[it1] = carry % BASE >>> 0; + b58[it1] = (carry % BASE) >>> 0; carry = (carry / BASE) >>> 0; } if (carry !== 0) { @@ -214,7 +214,7 @@ goog.scope(function () { it3--, i++ ) { carry += (BASE * b256[it3]) >>> 0; - b256[it3] = carry % 256 >>> 0; + b256[it3] = (carry % 256) >>> 0; carry = (carry / 256) >>> 0; } if (carry !== 0) { diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 0584d0f0f7..5c69794bf3 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -169,6 +169,7 @@ :mcp :background-blur + :available-viewer-wasm :stroke-path}) (def all-flags @@ -194,6 +195,7 @@ :enable-render-wasm-dpr :enable-token-color :enable-token-shadow + :enable-token-typography-row :enable-inspect-styles :enable-feature-fdata-objects-map :enable-feature-render-wasm diff --git a/common/src/app/common/geom/shapes/bounds.cljc b/common/src/app/common/geom/shapes/bounds.cljc index 3da3a1eef8..95b215c686 100644 --- a/common/src/app/common/geom/shapes/bounds.cljc +++ b/common/src/app/common/geom/shapes/bounds.cljc @@ -89,14 +89,23 @@ ([shape] (get-shape-filter-bounds shape false)) ([shape ignore-shadow-margin?] - (if (or (and (cfh/svg-raw-shape? shape) - (not= :svg (dm/get-in shape [:content :tag]))) - ;; If no shadows or blur, we return the selrect as is - (and (empty? (-> shape :shadow)) - (or (nil? (:blur shape)) - (not= :layer-blur (-> shape :blur :type)) - (zero? (-> shape :blur :value (or 0)))))) + (cond + ;; SVG raw elements (non-root) don't have proper rotated points; use selrect + (and (cfh/svg-raw-shape? shape) + (not= :svg (dm/get-in shape [:content :tag]))) (dm/get-prop shape :selrect) + + ;; No shadows or blur: use the axis-aligned bounding box from the actual + ;; (possibly rotated) points. Using selrect here would be wrong for rotated + ;; shapes because selrect stores the unrotated rectangle, not the screen-space bbox. + (and (empty? (-> shape :shadow)) + (or (nil? (:blur shape)) + (not= :layer-blur (-> shape :blur :type)) + (zero? (-> shape :blur :value (or 0))))) + (-> (dm/get-prop shape :points) + (grc/points->rect)) + + :else (let [filters (shape->filters shape) blur-value (case (-> shape :blur :type) :layer-blur (or (-> shape :blur :value) 0) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index cec89730f9..d18f7019ec 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -2101,6 +2101,39 @@ (grc/rect->center selrect) (or (:transform current-shape) (gmt/matrix))))))) + +(defn- switch-geom-change-value + [prev-shape current-shape attr] + ;; Composite geometry stores absolute coordinates. When preserving a size + ;; override across variants, keep the target variant's position and only carry + ;; the previous dimensions; otherwise :x/:y can disagree with :selrect/:points. + (let [prev-selrect (:selrect prev-shape) + current-selrect (:selrect current-shape) + final-width (:width prev-selrect) + final-height (:height prev-selrect) + x (:x current-selrect) + y (:y current-selrect) + selrect (assoc current-selrect + :width final-width + :height final-height + :x x + :y y + :x1 x + :y1 y + :x2 (+ x final-width) + :y2 (+ y final-height))] + (case attr + :selrect + selrect + + :points + (-> selrect + (grc/rect->points) + (gsh/transform-points + (grc/rect->center selrect) + (or (:transform current-shape) (gmt/matrix))))))) + + (defn- equal-geometry? "Returns true when the value of `attr` in `shape` is considered equal to the corresponding value in `origin-shape`, ignoring positional @@ -2270,6 +2303,10 @@ (contains? #{:points :selrect :width :height} attr)) (switch-fixed-layout-geom-change-value previous-shape current-shape origin-ref-shape attr) + (and (contains? #{:points :selrect} attr) + (not path-change?)) + (switch-geom-change-value previous-shape current-shape attr) + :else (get previous-shape attr))) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 8ba699984c..fba8169bcd 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -448,18 +448,22 @@ ::oapi/type "string" ::oapi/format "uuid"}}) -(def email-re #"[a-zA-Z0-9_.+-\\\\]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+") +;; Strict email regex aligned with app.common.spec/email-re. +;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots. +;; Domain: labels can't start/end with hyphen, no empty labels. +;; TLD: at least 2 alphabetic chars. +(def email-re + #"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,63}") (defn parse-email [s] - (if (string? s) - (first (re-seq email-re s)) - nil)) + (when (and (string? s) (re-matches email-re s)) + s)) (defn email-string? [s] (and (string? s) - (re-seq email-re s))) + (some? (re-matches email-re s)))) (register! {:type ::email diff --git a/common/src/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js index 45582c2604..9a868cc9a5 100644 --- a/common/src/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -217,11 +217,11 @@ goog.scope(function () { // Parse ........-....-....-####-............ int8[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8; - (int8[9] = rest & 0xff), + ((int8[9] = rest & 0xff), // Parse ........-....-....-....-############ // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) (int8[10] = - ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff); + ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff)); int8[11] = (rest / 0x100000000) & 0xff; int8[12] = (rest >>> 24) & 0xff; int8[13] = (rest >>> 16) & 0xff; diff --git a/common/test/common_tests/logic/variants_switch_test.cljc b/common/test/common_tests/logic/variants_switch_test.cljc index f796f59c5d..ed9eeae783 100644 --- a/common/test/common_tests/logic/variants_switch_test.cljc +++ b/common/test/common_tests/logic/variants_switch_test.cljc @@ -2866,18 +2866,14 @@ (t/is (= (get-in rect02' [:selrect :width]) 150)))) -(t/deftest test-switch-when-source-master-child-has-touched-geometry - ;; Regression: when the previous-shape's geometry has sub-pixel drift +(t/deftest test-switch-skips-composite-geometry-with-subpixel-drift + ;; Regression: when the previous-shape's geometry only has sub-pixel drift ;; relative to its source master (a state produced by interactive transform ;; modifiers, e.g. alt-drag duplicate of a variant whose children are - ;; component copies), the equal-geometry? guard in update-attrs-on-switch - ;; uses exact equality and fails. The :else branch then copies - ;; previous-shape's :selrect verbatim onto the freshly-instantiated target, - ;; leaving :y correct (the per-attr y skip catches that) but :selrect.y - ;; stale. The shape ends up internally inconsistent (:y disagrees with - ;; :selrect.y); the renderer reads :selrect, so the child appears at the - ;; source variant's position inside a parent that has resized to the - ;; target's dimensions — the visible "cut off" symptom. + ;; component copies), equal-geometry? must classify it as unchanged and skip + ;; copying composite geometry. Otherwise, :selrect/:points can carry stale + ;; absolute positions from the source variant onto the freshly-instantiated + ;; target, producing the visible "cut off" symptom. (let [;; ==== Setup ;; A self-contained Input/Button-like component, plus a variant ;; container whose two variants each instance that component @@ -2911,8 +2907,8 @@ ;; The copy carries an Input/Button instance (Frame1). Introduce ;; sub-pixel drift in its :width and :selrect.width — the kind of ;; floating-point error produced by the alt-drag modifier path in - ;; production. This drift is what defeats equal-geometry?'s - ;; exact-equality comparison and lets the bug surface. + ;; production. The drift is small enough to be treated as unchanged + ;; geometry by equal-geometry?. page (thf/current-page file) copy01 (ths/get-shape file :copy01) copy-btn-id (->> (cfh/get-children-ids-with-self (:objects page) (:id copy01)) @@ -3035,3 +3031,73 @@ (t/is (= target-rel-y actual-rel-y) (str "path :selrect.y should match target master layout (expected " target-rel-y " got " actual-rel-y ")")))) + + +(t/deftest test-switch-preserves-size-override-at-target-position + (let [move-to (fn [shape x y] + (gsh/move shape (gpt/point (- x (:x shape)) + (- y (:y shape))))) + + ;; ==== Setup: each variant contains the same nested component instance. + ;; The nested instance has identical size in both variants, but a different + ;; position relative to the variant root. + file (-> (thf/sample-file :file1) + (tho/add-simple-component + :nested-component :nested-main :nested-label + :root-params {:width 100 :height 50} + :child-params {:width 30 :height 10}) + (thv/add-variant-with-copy + :v01 :c01 :m01 :c02 :m02 :r01 :r02 :nested-component)) + + page (thf/current-page file) + r01 (ths/get-shape file :r01) + r02 (ths/get-shape file :r02) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id r01) (:id r02)} + (fn [shape] + (cond + (= (:id shape) (:id r01)) (move-to shape 20 100) + (= (:id shape) (:id r02)) (move-to shape 20 70) + :else shape)) + (:objects page) + {}) + file (thf/apply-changes file changes) + + file (thc/instantiate-component file :c01 + :copy01 + :children-labels [:copy-r01]) + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + copy-r01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; This is a real geometry override, not float drift. The switch should + ;; preserve the overridden size while anchoring composite geometry to + ;; the target variant's position. + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-r01)} + (fn [shape] + (let [new-width 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :x2 (+ (:x1 sr) new-width)))] + (-> shape + (assoc :width new-width) + (assoc :selrect new-sr) + (assoc :touched #{:geometry-group})))) + (:objects page) + {}) + file (thf/apply-changes file changes) + + ;; ==== Action + file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The width override is preserved, but the target variant position remains + ;; authoritative for absolute composite geometry. + (t/is (= 150 (:width rect02'))) + (t/is (= (+ (:y copy02') 70) (:y rect02'))) + (t/is (= (:y rect02') (get-in rect02' [:selrect :y]))))) diff --git a/common/test/common_tests/schema_test.cljc b/common/test/common_tests/schema_test.cljc index b94d4ae4a4..b14f1df0f5 100644 --- a/common/test/common_tests/schema_test.cljc +++ b/common/test/common_tests/schema_test.cljc @@ -188,3 +188,60 @@ (t/is (= false (decode-s "f"))) (t/is (= true (decode-s "1"))) (t/is (= false (decode-s "0"))))) + +(t/deftest test-email-validation + (t/testing "accepts well-formed email addresses" + (doseq [email ["user@domain.com" + "user.name@domain.com" + "user+tag@domain.com" + "user-name@domain.com" + "user_name@domain.com" + "user123@domain.com" + "USER@DOMAIN.COM" + "u@domain.io" + "user@sub.domain.com" + "user@domain.co.uk" + "user@domain.dev" + "a@bc.co"]] + (t/is (sm/validate ::sm/email email) (str "should accept: " email)) + (t/is (= email (sm/decode ::sm/email email sm/json-transformer))))) + + (t/testing "rejects domain with consecutive dots (dot-dot)" + (t/is (false? (sm/validate ::sm/email "user@gmail.com.."))) + (t/is (false? (sm/validate ::sm/email "user@sub..domain.com"))) + (t/is (false? (sm/validate ::sm/email "eissaalbothigi@gmail.com.."))) + (t/is (nil? (sm/parse-email "user@gmail.com.."))) + (t/is (nil? (sm/parse-email "eissaalbothigi@gmail.com..")))) + + (t/testing "rejects domain ending with a dot" + (t/is (false? (sm/validate ::sm/email "user@domain."))) + (t/is (nil? (sm/parse-email "user@domain.")))) + + (t/testing "rejects domain starting with a dot" + (t/is (false? (sm/validate ::sm/email "user@.domain.com"))) + (t/is (nil? (sm/parse-email "user@.domain.com")))) + + (t/testing "rejects local part with consecutive dots" + (t/is (false? (sm/validate ::sm/email "user..name@domain.com"))) + (t/is (nil? (sm/parse-email "user..name@domain.com")))) + + (t/testing "rejects local part starting with a dot" + (t/is (false? (sm/validate ::sm/email ".user@domain.com"))) + (t/is (nil? (sm/parse-email ".user@domain.com")))) + + (t/testing "rejects label starting or ending with hyphen" + (t/is (false? (sm/validate ::sm/email "user@-domain.com"))) + (t/is (false? (sm/validate ::sm/email "user@domain-.com")))) + + (t/testing "rejects TLD shorter than 2 chars" + (t/is (false? (sm/validate ::sm/email "user@domain.c")))) + + (t/testing "rejects domain without a dot" + (t/is (false? (sm/validate ::sm/email "user@domain")))) + + (t/testing "rejects empty or malformed emails" + (t/is (false? (sm/validate ::sm/email ""))) + (t/is (false? (sm/validate ::sm/email "@domain.com"))) + (t/is (false? (sm/validate ::sm/email "user@"))) + (t/is (false? (sm/validate ::sm/email "userdomain.com"))) + (t/is (false? (sm/validate ::sm/email "user@@domain.com"))))) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 24ec6dfd54..ed3e672773 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 AS base +FROM ubuntu:26.04 AS base ENV LANG='C.UTF-8' \ LC_ALL='C.UTF-8' \ @@ -32,7 +32,7 @@ RUN set -ex; \ FROM base AS setup-node -ENV NODE_VERSION=v24.15.0 \ +ENV NODE_VERSION=v24.16.0 \ PATH=/opt/node/bin:$PATH RUN set -eux; \ @@ -416,7 +416,7 @@ RUN set -ex; \ libfreetype6 \ libfontconfig1 \ libglib2.0-0 \ - libxml2 \ + libxml2-16 \ liblcms2-2 \ libheif1 \ libopenjp2-7 \ @@ -425,7 +425,7 @@ RUN set -ex; \ libgomp1 \ libwebpmux3 \ libwebpdemux2 \ - libzip4t64 \ + libzip5 \ ; \ rm -rf /var/lib/apt/lists/*; @@ -458,7 +458,7 @@ ENV LANG='C.UTF-8' \ SERENA_CONTEXT="claude-code" \ PATH="/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" -COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick +COPY --from=penpotapp/imagemagick:7.1.2-24 /opt/imagick /opt/imagick COPY --from=setup-jvm /opt/jdk /opt/jdk COPY --from=setup-jvm /opt/clojure /opt/clojure COPY --from=setup-node /opt/node /opt/node diff --git a/docker/imagemagick/Dockerfile b/docker/imagemagick/Dockerfile index d06b0d2d3a..e53fb65d8a 100644 --- a/docker/imagemagick/Dockerfile +++ b/docker/imagemagick/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 +FROM ubuntu:26.04 LABEL maintainer="Penpot <docker@penpot.app>" ENV LANG='C.UTF-8' \ @@ -6,7 +6,7 @@ ENV LANG='C.UTF-8' \ DEBIAN_FRONTEND=noninteractive \ TZ=Etc/UTC -ARG IMAGEMAGICK_VERSION=7.1.2-13 +ARG IMAGEMAGICK_VERSION=7.1.2-24 RUN set -e; \ apt-get -qq update; \ @@ -79,13 +79,12 @@ RUN set -e; \ libopenjp2-7 \ libpng16-16 \ librsvg2-2 \ - libxml2 \ libtiff6 \ libwebp7 \ libwebpdemux2 \ libwebpmux3 \ - libxml2 \ - libzip4t64 \ + libxml2-16 \ + libzip5 \ libzstd1 \ ;\ apt-get -qqy clean; \ diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend index c27d2c4363..46dc7277aa 100644 --- a/docker/images/Dockerfile.backend +++ b/docker/images/Dockerfile.backend @@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \ LC_ALL='C.UTF-8' \ JAVA_HOME="/opt/jdk" \ DEBIAN_FRONTEND=noninteractive \ - NODE_VERSION=v24.15.0 \ + NODE_VERSION=v24.16.0 \ TZ=Etc/UTC RUN set -ex; \ @@ -16,6 +16,7 @@ RUN set -ex; \ ca-certificates \ curl \ ; \ + apt-get clean; \ rm -rf /var/lib/apt/lists/* RUN set -eux; \ @@ -115,13 +116,13 @@ RUN set -ex; \ woff2 \ ; \ find tmp/usr/share/zoneinfo/* -type d ! -name 'Etc' |xargs rm -rf; \ + apt-get clean; \ rm -rf /var/lib /var/cache; \ rm -rf /usr/include; \ mkdir -p /opt/data/assets; \ mkdir -p /opt/penpot; \ chown -R penpot:penpot /opt/penpot; \ - chown -R penpot:penpot /opt/data; \ - rm -rf /var/lib/apt/lists/*; + chown -R penpot:penpot /opt/data; COPY --from=build /opt/jre /opt/jre COPY --from=build /opt/node /opt/node diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 0049c6dd76..2d028b5ff6 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>" ENV LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ - NODE_VERSION=v24.15.0 \ + NODE_VERSION=v24.16.0 \ DEBIAN_FRONTEND=noninteractive \ PATH=/opt/node/bin:/opt/imagick/bin:$PATH \ PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers @@ -13,12 +13,14 @@ RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \ apt-get -qq update; \ + apt-get -qq upgrade; \ apt-get -qqy --no-install-recommends install \ curl \ tzdata \ locales \ ca-certificates \ ; \ + apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ locale-gen; \ @@ -80,6 +82,7 @@ RUN set -ex; \ libzip4t64 \ libzstd1 \ ; \ + apt-get clean; \ rm -rf /var/lib/apt/lists/*; RUN set -eux; \ diff --git a/docker/images/Dockerfile.frontend b/docker/images/Dockerfile.frontend index f77a6d187a..fe1ba7833c 100644 --- a/docker/images/Dockerfile.frontend +++ b/docker/images/Dockerfile.frontend @@ -1,10 +1,15 @@ -FROM nginxinc/nginx-unprivileged:1.30.0 +FROM nginxinc/nginx-unprivileged:1.30.2-alpine LABEL maintainer="Penpot <docker@penpot.app>" USER root RUN set -ex; \ - useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \ + apk update; \ + apk upgrade; \ + apk add --no-cache bash gettext; \ + rm -rf /var/cache/apk/*; \ + addgroup -g 1001 penpot; \ + adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot; \ mkdir -p /opt/data/assets; \ chown -R penpot:penpot /opt/data; \ mkdir -p /etc/nginx/overrides/main.d/; \ diff --git a/docker/images/Dockerfile.mcp b/docker/images/Dockerfile.mcp index 14b1172035..f13fbd5e99 100644 --- a/docker/images/Dockerfile.mcp +++ b/docker/images/Dockerfile.mcp @@ -13,12 +13,14 @@ RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \ apt-get -qq update; \ + apt-get -qq upgrade; \ apt-get -qqy --no-install-recommends install \ curl \ tzdata \ locales \ ca-certificates \ ; \ + apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ locale-gen; \ diff --git a/docker/images/Dockerfile.storybook b/docker/images/Dockerfile.storybook index 9cccbe799b..9eb5a55663 100644 --- a/docker/images/Dockerfile.storybook +++ b/docker/images/Dockerfile.storybook @@ -1,10 +1,14 @@ -FROM nginxinc/nginx-unprivileged:1.30.0 +FROM nginxinc/nginx-unprivileged:1.30.2-alpine LABEL maintainer="Penpot <docker@penpot.app>" USER root RUN set -ex; \ - useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; + apk update; \ + apk upgrade; \ + rm -rf /var/cache/apk/*; \ + addgroup -g 1001 penpot; \ + adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot; ARG BUNDLE_PATH="./bundle-storybook/" COPY $BUNDLE_PATH /var/www/ diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index f6979e5e43..94193a977c 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -78,7 +78,7 @@ services: # - "443:443" penpot-frontend: - image: "penpotapp/frontend:${PENPOT_VERSION:-2.15}" + image: "penpotapp/frontend:${PENPOT_VERSION:-2.16}" restart: always ports: - 9001:8080 @@ -111,7 +111,7 @@ services: # PENPOT_DISABLE_IPV6_LISTEN: "true" penpot-backend: - image: "penpotapp/backend:${PENPOT_VERSION:-2.15}" + image: "penpotapp/backend:${PENPOT_VERSION:-2.16}" restart: always volumes: @@ -180,13 +180,13 @@ services: PENPOT_SMTP_SSL: "false" penpot-mcp: - image: "penpotapp/mcp:${PENPOT_VERSION:-2.15}" + image: "penpotapp/mcp:${PENPOT_VERSION:-2.16}" restart: always networks: - penpot penpot-exporter: - image: "penpotapp/exporter:${PENPOT_VERSION:-2.15}" + image: "penpotapp/exporter:${PENPOT_VERSION:-2.16}" restart: always depends_on: diff --git a/docs/css/index.css b/docs/css/index.css index 63282f168a..9737456609 100644 --- a/docs/css/index.css +++ b/docs/css/index.css @@ -848,6 +848,9 @@ a[href].post-tag:visited { .illus-techguide { background-image: url(/img/home-technical-guide.webp); } +.illus-mcp { + background-image: url(/img/home-mcp-server.webp); +} .illus-plugins { background-image: url(/img/home-plugins.webp); } diff --git a/docs/index.njk b/docs/index.njk index ef15854588..908d2f00b0 100644 --- a/docs/index.njk +++ b/docs/index.njk @@ -18,24 +18,30 @@ eleventyNavigation: <p>Everything you need to know about how Penpot works.</p> </a> </li> - <li class="illus illus-contributing"> - <a href="/contributing-guide/"> - <h2>Contributing guide →</h2> - <p>How to report bugs, add translations and more.</p> - </a> - </li> <li class="illus illus-techguide"> <a href="/technical-guide/"> <h2>Technical guide →</h2> <p>Installation, configuration and architecture.</p> </a> </li> + <li class="illus illus-mcp"> + <a href="/mcp/"> + <h2>MCP server →</h2> + <p>Connect AI agents to your Penpot files for design and development workflows.</p> + </a> + </li> <li class="illus illus-plugins"> <a href="/plugins/"> <h2>Plugins →</h2> <p>All about Penpot plugins.</p> </a> </li> + <li class="illus illus-contributing"> + <a href="/contributing-guide/"> + <h2>Contributing guide →</h2> + <p>How to report bugs, add translations and more.</p> + </a> + </li> <li class="illus illus-faq"> <a href="https://community.penpot.app/c/faq/17"> <h2>FAQs →</h2> diff --git a/docs/mcp/index.md b/docs/mcp/index.md index e0d31fe6ce..466144b3f1 100644 --- a/docs/mcp/index.md +++ b/docs/mcp/index.md @@ -309,6 +309,38 @@ For client-specific setup, use the shared section **Connect your MCP client**. For remote mode, use the URL shown in **Your account → Integrations → MCP Server**, which includes your `userToken`. +### Setup videos + +<figure> + <iframe + width="672px" + height="378px" + src="https://www.youtube.com/embed/hBn1iutWSq4?rel=0" + title="Penpot MCP Server: Remote Setup in 5 Min | OpenCode + OpenRouter" + frameborder="0" + loading="lazy" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen> + </iframe> + <figcaption>Penpot MCP Server – Remote setup with OpenCode and OpenRouter</figcaption> +</figure> + +<figure> + <iframe + width="672px" + height="378px" + src="https://www.youtube.com/embed/QeSFO0h7GGY?rel=0" + title="Penpot MCP Server Remote Setup in 1 Minute | Cursor" + frameborder="0" + loading="lazy" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen> + </iframe> + <figcaption>Penpot MCP Server – Remote setup with Cursor</figcaption> +</figure> + <a id="use-remote"></a> ### Use @@ -391,6 +423,23 @@ Leave this terminal running while you use MCP. For advanced or repository-based workflows, see the [MCP README](https://github.com/penpot/penpot/blob/main/mcp/README.md) in the Penpot repository. +### Setup video + +<figure> + <iframe + width="672px" + height="378px" + src="https://www.youtube.com/embed/xfgf0kKGOoc?rel=0" + title="Penpot MCP Server Local Setup" + frameborder="0" + loading="lazy" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" + referrerpolicy="strict-origin-when-cross-origin" + allowfullscreen> + </iframe> + <figcaption>Penpot MCP Server – Local setup</figcaption> +</figure> + <a id="connect-local"></a> ### Connect diff --git a/docs/package.json b/docs/package.json index 0a11edb8e5..3d65be9fa8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -39,5 +39,5 @@ "markdown-it-anchor": "^9.0.1", "markdown-it-plantuml": "^1.4.1" }, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268" + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index aca6a50724..6bac802608 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -31,43 +31,58 @@ importers: version: 0.9.5 eleventy-plugin-metagen: specifier: ^1.8.3 - version: 1.8.3 + version: 1.8.4 eleventy-plugin-nesting-toc: specifier: ^1.3.0 version: 1.3.0 eleventy-plugin-youtube-embed: specifier: ^1.10.2 - version: 1.13.1 + version: 1.13.2 luxon: specifier: ^3.4.4 version: 3.7.2 markdown-it: specifier: ^14.1.0 - version: 14.1.0 + version: 14.2.0 markdown-it-anchor: specifier: ^9.0.1 - version: 9.2.0(@types/markdown-it@14.1.0)(markdown-it@14.1.0) + version: 9.2.0(@types/markdown-it@14.1.0)(markdown-it@14.2.0) markdown-it-plantuml: specifier: ^1.4.1 version: 1.4.1 packages: + '@11ty/dependency-tree-esm@2.0.4': + resolution: {integrity: sha512-MYKC0Ac77ILr1HnRJalzKDlb9Z8To3kXQCltx299pUXXUFtJ1RIONtULlknknqW8cLe19DLVgmxVCtjEFm7h0A==} + '@11ty/dependency-tree@2.0.1': resolution: {integrity: sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==} + '@11ty/dependency-tree@4.0.2': + resolution: {integrity: sha512-RTF6VTZHatYf7fSZBUN3RKwiUeJh5dhWV61gDPrHhQF2/gzruAkYz8yXuvGLx3w3ZBKreGrR+MfYpSVkdbdbLA==} + '@11ty/eleventy-dev-server@1.0.4': resolution: {integrity: sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==} engines: {node: '>=14'} hasBin: true - '@11ty/eleventy-fetch@5.1.1': - resolution: {integrity: sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==} + '@11ty/eleventy-dev-server@2.0.8': + resolution: {integrity: sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==} + engines: {node: '>=18'} + hasBin: true + + '@11ty/eleventy-fetch@5.1.2': + resolution: {integrity: sha512-YxDARdR3S9UT4gOGRWgGNyokYT9jkCAjJge3OVKFdNMv1cyvWg1NHCvj9NVvK9XHenCLFy3cwvM/YYpZZTZopw==} engines: {node: '>=18'} '@11ty/eleventy-navigation@0.3.5': resolution: {integrity: sha512-4aKW5aIQDFed8xs1G1pWcEiFPcDSwZtA4IH1eERtoJ+Xy+/fsoe0pzbDmw84bHZ9ACny5jblENhfZhcCxklqQw==} + '@11ty/eleventy-plugin-bundle@3.0.7': + resolution: {integrity: sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==} + engines: {node: '>=18'} + '@11ty/eleventy-plugin-rss@1.2.0': resolution: {integrity: sha512-YzFnSH/5pObcFnqZ2sAQ782WmpOZHj1+xB9ydY/0j7BZ2jUNahn53VmwCB/sBRwXA/Fbwwj90q1MLo01Ru0UaQ==} @@ -87,25 +102,38 @@ packages: engines: {node: '>=14'} hasBin: true + '@11ty/eleventy@3.1.6': + resolution: {integrity: sha512-ZlSiR1PLdS2lv7TelBgWAhcvMiLNZkPBlLEb+lh7kGYZ+Mk0bo9qcYgVsewvw9W7Em0RH3wd01h5fAstNDh0zA==} + engines: {node: '>=18'} + hasBin: true + '@11ty/lodash-custom@4.17.21': resolution: {integrity: sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==} engines: {node: '>=14'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@11ty/posthtml-urls@1.0.3': + resolution: {integrity: sha512-1YvhnkaNlFnnJic1rBMWmTC2adbuy+JQiBfl1Hecr1Wjjik1pQZmGyk/eC9zKX/FQv52s2Nht1Gi/UwhYqrBeg==} + engines: {node: '>= 6'} + + '@11ty/recursive-copy@4.0.4': + resolution: {integrity: sha512-oI7m8pa7/IAU/3lqRU9vjBbs20iKFo7x+1K9kT3aVira6scc1X9MjBdgLCHzLJeJ7iB6wydioA+kr9/qPnvmlQ==} + engines: {node: '>=18'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} '@iarna/toml@2.2.5': @@ -131,10 +159,18 @@ packages: resolution: {integrity: sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==} engines: {node: '>=10'} + '@sindresorhus/slugify@2.2.1': + resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} + engines: {node: '>=12'} + '@sindresorhus/transliterate@0.1.2': resolution: {integrity: sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==} engines: {node: '>=10'} + '@sindresorhus/transliterate@1.6.0': + resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} + engines: {node: '>=12'} + '@tigersway/eleventy-plugin-ancestry@1.0.3': resolution: {integrity: sha512-ZdJTrD7+B2L+kGGGj+rqqP/NhScXzrlgYqe40o2fkTDmG17ToOvO7DajAQcxmvRRVoWjf9VZn5nZRpAYdlzAIA==} peerDependencies: @@ -155,11 +191,20 @@ packages: a-sync-waterfall@1.0.1: resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + any-promise@0.1.0: resolution: {integrity: sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==} @@ -220,12 +265,21 @@ packages: bcp-47-match@1.0.3: resolution: {integrity: sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==} + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + bcp-47-normalize@1.1.1: resolution: {integrity: sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==} + bcp-47-normalize@2.3.0: + resolution: {integrity: sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==} + bcp-47@1.0.8: resolution: {integrity: sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==} + bcp-47@2.1.0: + resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -233,11 +287,11 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -257,8 +311,8 @@ packages: cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - cheerio@1.1.2: - resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} + cheerio@1.2.0: + resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} chokidar@3.6.0: @@ -311,10 +365,18 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dependency-graph@0.11.0: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} + dependency-graph@1.0.0: + resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} + engines: {node: '>=4'} + dev-ip@1.0.1: resolution: {integrity: sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==} engines: {node: '>= 0.8.0'} @@ -361,14 +423,14 @@ packages: elasticlunr@0.9.5: resolution: {integrity: sha512-5YM9LFQgVYfuLNEoqMqVWIBuF2UNCA+xu/jz1TyryLN/wmBcQSb+GNAwvLKvEpGESwgGN8XA1nbLAt6rKlyHYQ==} - eleventy-plugin-metagen@1.8.3: - resolution: {integrity: sha512-PP0pQrY/2Jeo5BgBW3xJyBkNhMW0/xOOMBaJkZTJMCT1uOaEFnN0vYPZ3FgD3rvkmhAAVSBwBh172x/E/Y6uQw==} + eleventy-plugin-metagen@1.8.4: + resolution: {integrity: sha512-v8yIjwx7W/A6HKktVltk601KQUb3Sy/syHhtKXmlYMsb7IvIp17KugK3dko8z1oy+z2GDV57PY7vXZLtZs7Fzw==} eleventy-plugin-nesting-toc@1.3.0: resolution: {integrity: sha512-WZzVkz28nw3A0DpJFQWXZzQxniyjvOZKxVix5x7WVAS0H1OD1hYJbR0go/Lr1kK2SrmxfbsUHUlekR+mQPvqMA==} - eleventy-plugin-youtube-embed@1.13.1: - resolution: {integrity: sha512-JfmPyNan5x9FC08RjLQIeBexpo1u+g06FCnEuQMkRbORh4dy/6LILJGcsPkrAHP6fw7ZnD/bogfTTuW5lCyukA==} + eleventy-plugin-youtube-embed@1.13.2: + resolution: {integrity: sha512-dd5VnphtAvF4lWbZ/Z5kXnkcGLVYTUIYtBsbOTNQ5PfU+dZ/qRgsXvtzaH4+/txBbcsSm9AtRi5mtYHgD2WNPA==} encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} @@ -392,10 +454,18 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true + errno@1.0.0: + resolution: {integrity: sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==} + hasBin: true + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -404,8 +474,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} escape-html@1.0.3: @@ -419,11 +489,26 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esm-import-transformer@3.0.5: + resolution: {integrity: sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + evaluate-value@2.0.0: + resolution: {integrity: sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==} + engines: {node: '>= 8'} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -438,8 +523,21 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + filesize@10.1.6: + resolution: {integrity: sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==} + engines: {node: '>= 10.4.0'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -449,8 +547,12 @@ packages: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -480,7 +582,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -496,8 +598,8 @@ packages: hamljs@0.6.2: resolution: {integrity: sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} engines: {node: '>=0.4.7'} hasBin: true @@ -509,12 +611,12 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} - htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} htmlparser2@7.2.0: resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} @@ -523,6 +625,14 @@ packages: resolution: {integrity: sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==} engines: {node: '>= 0.10'} + http-equiv-refresh@2.0.1: + resolution: {integrity: sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==} + engines: {node: '>= 6'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -537,20 +647,29 @@ packages: is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + is-alphanumerical@1.0.4: resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-decimal@1.0.4: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-expression@4.0.0: resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} @@ -587,6 +706,10 @@ packages: resolution: {integrity: sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==} engines: {node: '>=6.0'} + iso-639-1@3.1.5: + resolution: {integrity: sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==} + engines: {node: '>=6.0'} + jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -599,6 +722,10 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + jstransformer@1.0.0: resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} @@ -606,6 +733,10 @@ packages: resolution: {integrity: sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==} engines: {node: '>=0.10.0'} + junk@3.1.0: + resolution: {integrity: sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==} + engines: {node: '>=8'} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -617,11 +748,11 @@ packages: linkify-it@4.0.1: resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + linkify-it@5.0.1: + resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==} - liquidjs@10.24.0: - resolution: {integrity: sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==} + liquidjs@10.27.0: + resolution: {integrity: sha512-tw/OA59K7aIBlMKIrKlumr37fiZUheShVHXY8cVctWisgY1p9mc5hreOvlreoS0wTiwlWk14Ya7305c2a/Cg5w==} engines: {node: '>=16'} hasBin: true @@ -651,8 +782,8 @@ packages: resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==} hasBin: true - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + markdown-it@14.2.0: + resolution: {integrity: sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==} hasBin: true math-intrinsics@1.1.0: @@ -680,16 +811,24 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} hasBin: true - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} minimist@1.2.8: @@ -699,6 +838,10 @@ packages: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -706,8 +849,11 @@ packages: moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} - morphdom@2.7.7: - resolution: {integrity: sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==} + moo@0.5.3: + resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} + + morphdom@2.7.8: + resolution: {integrity: sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -726,6 +872,9 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-retrieve-globals@6.0.1: + resolution: {integrity: sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -799,10 +948,14 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -810,6 +963,12 @@ packages: please-upgrade-node@3.2.0: resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} + posthtml-match-helper@2.0.3: + resolution: {integrity: sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==} + engines: {node: '>=18'} + peerDependencies: + posthtml: ^0.16.6 + posthtml-parser@0.11.0: resolution: {integrity: sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==} engines: {node: '>=12'} @@ -842,8 +1001,8 @@ packages: pug-attrs@3.0.0: resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} - pug-code-gen@3.0.3: - resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==} + pug-code-gen@3.0.4: + resolution: {integrity: sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==} pug-error@2.1.0: resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} @@ -872,8 +1031,8 @@ packages: pug-walk@2.0.0: resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} - pug@3.0.3: - resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} + pug@3.0.4: + resolution: {integrity: sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==} punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} @@ -882,6 +1041,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -889,8 +1052,8 @@ packages: recursive-copy@2.0.14: resolution: {integrity: sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -916,11 +1079,18 @@ packages: semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.8.3: + resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==} engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -933,8 +1103,12 @@ packages: resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==} engines: {node: '>=0.10.0'} - slugify@1.6.6: - resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slugify@1.6.9: + resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} source-map@0.6.1: @@ -944,6 +1118,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + ssri@11.0.0: + resolution: {integrity: sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==} + engines: {node: ^16.14.0 || >=18.0.0} + ssri@8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} engines: {node: '>= 8'} @@ -964,10 +1142,18 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + token-stream@1.0.0: resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} @@ -982,14 +1168,17 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - undici@7.18.2: - resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} + undici@7.27.2: + resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} engines: {node: '>=20.18.1'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -1018,8 +1207,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1035,8 +1224,19 @@ packages: snapshots: + '@11ty/dependency-tree-esm@2.0.4': + dependencies: + '@11ty/eleventy-utils': 2.0.7 + acorn: 8.16.0 + dependency-graph: 1.0.0 + normalize-path: 3.0.0 + '@11ty/dependency-tree@2.0.1': {} + '@11ty/dependency-tree@4.0.2': + dependencies: + '@11ty/eleventy-utils': 2.0.7 + '@11ty/eleventy-dev-server@1.0.4': dependencies: '@11ty/eleventy-utils': 1.0.3 @@ -1046,21 +1246,40 @@ snapshots: finalhandler: 1.3.2 mime: 3.0.0 minimist: 1.2.8 - morphdom: 2.7.7 + morphdom: 2.7.8 please-upgrade-node: 3.2.0 ssri: 8.0.1 - ws: 8.19.0 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@11ty/eleventy-fetch@5.1.1': + '@11ty/eleventy-dev-server@2.0.8': + dependencies: + '@11ty/eleventy-utils': 2.0.7 + chokidar: 3.6.0 + debug: 4.4.3 + finalhandler: 1.3.2 + mime: 3.0.0 + minimist: 1.2.8 + morphdom: 2.7.8 + please-upgrade-node: 3.2.0 + send: 1.2.1 + ssri: 11.0.0 + urlpattern-polyfill: 10.1.0 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@11ty/eleventy-fetch@5.1.2': dependencies: '@11ty/eleventy-utils': 2.0.7 '@rgrove/parse-xml': 4.2.0 debug: 4.4.3 - flatted: 3.3.3 + flatted: 3.4.2 p-queue: 6.6.2 transitivePeerDependencies: - supports-color @@ -1069,6 +1288,15 @@ snapshots: dependencies: dependency-graph: 0.11.0 + '@11ty/eleventy-plugin-bundle@3.0.7(posthtml@0.16.7)': + dependencies: + '@11ty/eleventy-utils': 2.0.7 + debug: 4.4.3 + posthtml-match-helper: 2.0.3(posthtml@0.16.7) + transitivePeerDependencies: + - posthtml + - supports-color + '@11ty/eleventy-plugin-rss@1.2.0': dependencies: debug: 4.4.3 @@ -1105,16 +1333,16 @@ snapshots: graceful-fs: 4.2.11 gray-matter: 4.0.3 hamljs: 0.6.2 - handlebars: 4.7.8 + handlebars: 4.7.9 is-glob: 4.0.3 iso-639-1: 2.1.15 kleur: 4.1.5 - liquidjs: 10.24.0 + liquidjs: 10.27.0 luxon: 3.7.2 markdown-it: 13.0.2 micromatch: 4.0.8 minimist: 1.2.8 - moo: 0.5.2 + moo: 0.5.3 multimatch: 5.0.0 mustache: 4.2.0 normalize-path: 3.0.0 @@ -1123,10 +1351,50 @@ snapshots: please-upgrade-node: 3.2.0 posthtml: 0.16.7 posthtml-urls: 1.0.0 - pug: 3.0.3 + pug: 3.0.4 recursive-copy: 2.0.14 - semver: 7.7.3 - slugify: 1.6.6 + semver: 7.8.3 + slugify: 1.6.9 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@11ty/eleventy@3.1.6': + dependencies: + '@11ty/dependency-tree': 4.0.2 + '@11ty/dependency-tree-esm': 2.0.4 + '@11ty/eleventy-dev-server': 2.0.8 + '@11ty/eleventy-plugin-bundle': 3.0.7(posthtml@0.16.7) + '@11ty/eleventy-utils': 2.0.7 + '@11ty/lodash-custom': 4.17.21 + '@11ty/posthtml-urls': 1.0.3 + '@11ty/recursive-copy': 4.0.4 + '@sindresorhus/slugify': 2.2.1 + bcp-47-normalize: 2.3.0 + chokidar: 3.6.0 + debug: 4.4.3 + dependency-graph: 1.0.0 + entities: 6.0.1 + filesize: 10.1.6 + gray-matter: 4.0.3 + iso-639-1: 3.1.5 + js-yaml: 4.2.0 + kleur: 4.1.5 + liquidjs: 10.27.0 + luxon: 3.7.2 + markdown-it: 14.2.0 + minimist: 1.2.8 + moo: 0.5.2 + node-retrieve-globals: 6.0.1 + nunjucks: 3.2.4(chokidar@3.6.0) + picomatch: 4.0.4 + please-upgrade-node: 3.2.0 + posthtml: 0.16.7 + posthtml-match-helper: 2.0.3(posthtml@0.16.7) + semver: 7.8.3 + slugify: 1.6.9 + tinyglobby: 0.2.17 transitivePeerDependencies: - bufferutil - supports-color @@ -1134,18 +1402,32 @@ snapshots: '@11ty/lodash-custom@4.17.21': {} - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/parser@7.28.6': + '@11ty/posthtml-urls@1.0.3': dependencies: - '@babel/types': 7.28.6 + evaluate-value: 2.0.0 + http-equiv-refresh: 2.0.1 + list-to-array: 1.1.0 + parse-srcset: 1.0.2 - '@babel/types@7.28.6': + '@11ty/recursive-copy@4.0.4': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + errno: 1.0.0 + junk: 3.1.0 + minimatch: 3.1.5 + slash: 3.0.0 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@iarna/toml@2.2.5': {} @@ -1168,11 +1450,20 @@ snapshots: '@sindresorhus/transliterate': 0.1.2 escape-string-regexp: 4.0.0 + '@sindresorhus/slugify@2.2.1': + dependencies: + '@sindresorhus/transliterate': 1.6.0 + escape-string-regexp: 5.0.0 + '@sindresorhus/transliterate@0.1.2': dependencies: escape-string-regexp: 2.0.0 lodash.deburr: 4.1.0 + '@sindresorhus/transliterate@1.6.0': + dependencies: + escape-string-regexp: 5.0.0 + '@tigersway/eleventy-plugin-ancestry@1.0.3(@11ty/eleventy@2.0.1)': dependencies: '@11ty/eleventy': 2.0.1 @@ -1190,14 +1481,20 @@ snapshots: a-sync-waterfall@1.0.1: {} + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + acorn@7.4.1: {} + acorn@8.16.0: {} + any-promise@0.1.0: {} anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 argparse@1.0.10: dependencies: @@ -1229,33 +1526,46 @@ snapshots: babel-walk@3.0.0-canary-5: dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.7 balanced-match@1.0.2: {} bcp-47-match@1.0.3: {} + bcp-47-match@2.0.3: {} + bcp-47-normalize@1.1.1: dependencies: bcp-47: 1.0.8 bcp-47-match: 1.0.3 + bcp-47-normalize@2.3.0: + dependencies: + bcp-47: 2.1.0 + bcp-47-match: 2.0.3 + bcp-47@1.0.8: dependencies: is-alphabetical: 1.0.4 is-alphanumerical: 1.0.4 is-decimal: 1.0.4 + bcp-47@2.1.0: + dependencies: + is-alphabetical: 2.0.1 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + binary-extensions@2.3.0: {} boolbase@1.0.0: {} - brace-expansion@1.1.12: + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.1: dependencies: balanced-match: 1.0.2 @@ -1286,18 +1596,18 @@ snapshots: domhandler: 5.0.3 domutils: 3.2.2 - cheerio@1.1.2: + cheerio@1.2.0: dependencies: cheerio-select: 2.1.0 dom-serializer: 2.0.0 domhandler: 5.0.3 domutils: 3.2.2 encoding-sniffer: 0.2.1 - htmlparser2: 10.0.0 + htmlparser2: 10.1.0 parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.18.2 + undici: 7.27.2 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -1320,8 +1630,8 @@ snapshots: constantinople@4.0.1: dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 cross-spawn@7.0.6: dependencies: @@ -1349,8 +1659,12 @@ snapshots: deepmerge@4.3.1: {} + depd@2.0.0: {} + dependency-graph@0.11.0: {} + dependency-graph@1.0.0: {} + dev-ip@1.0.1: {} doctypes@1.1.0: {} @@ -1403,9 +1717,9 @@ snapshots: elasticlunr@0.9.5: {} - eleventy-plugin-metagen@1.8.3: + eleventy-plugin-metagen@1.8.4: dependencies: - '@11ty/eleventy': 2.0.1 + '@11ty/eleventy': 3.1.6 meta-generator: 0.1.5 transitivePeerDependencies: - bufferutil @@ -1414,11 +1728,11 @@ snapshots: eleventy-plugin-nesting-toc@1.3.0: dependencies: - cheerio: 1.1.2 + cheerio: 1.2.0 - eleventy-plugin-youtube-embed@1.13.1: + eleventy-plugin-youtube-embed@1.13.2: dependencies: - '@11ty/eleventy-fetch': 5.1.1 + '@11ty/eleventy-fetch': 5.1.2 deepmerge: 4.3.1 lite-youtube-embed: 0.3.4 string-replace-async: 3.0.2 @@ -1440,15 +1754,21 @@ snapshots: entities@6.0.1: {} + entities@7.0.1: {} + errno@0.1.8: dependencies: prr: 1.0.1 + errno@1.0.0: + dependencies: + prr: 1.0.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-object-atoms@1.1.1: + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -1458,8 +1778,18 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + + esm-import-transformer@3.0.5: + dependencies: + acorn: 8.16.0 + esprima@4.0.1: {} + etag@1.8.1: {} + + evaluate-value@2.0.0: {} + eventemitter3@4.0.7: {} extend-shallow@2.0.1: @@ -1478,9 +1808,15 @@ snapshots: dependencies: reusify: 1.1.0 - filelist@1.0.4: + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + filelist@1.0.6: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 + + filesize@10.1.6: {} fill-range@7.1.1: dependencies: @@ -1498,7 +1834,9 @@ snapshots: transitivePeerDependencies: - supports-color - flatted@3.3.3: {} + flatted@3.4.2: {} + + fresh@2.0.0: {} fs.realpath@1.0.0: {} @@ -1512,18 +1850,18 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-tag@0.1.10: {} @@ -1536,7 +1874,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -1553,7 +1891,7 @@ snapshots: hamljs@0.6.2: {} - handlebars@4.7.8: + handlebars@4.7.9: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -1568,16 +1906,16 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.2: + hasown@2.0.4: dependencies: function-bind: 1.1.2 - htmlparser2@10.0.0: + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.2.2 - entities: 6.0.1 + entities: 7.0.1 htmlparser2@7.2.0: dependencies: @@ -1588,6 +1926,16 @@ snapshots: http-equiv-refresh@1.0.0: {} + http-equiv-refresh@2.0.1: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -1601,21 +1949,30 @@ snapshots: is-alphabetical@1.0.4: {} + is-alphabetical@2.0.1: {} + is-alphanumerical@1.0.4: dependencies: is-alphabetical: 1.0.4 is-decimal: 1.0.4 + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 is-decimal@1.0.4: {} + is-decimal@2.0.1: {} + is-expression@4.0.0: dependencies: acorn: 7.4.1 @@ -1640,16 +1997,18 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 isexe@2.0.0: {} iso-639-1@2.1.15: {} + iso-639-1@3.1.5: {} + jake@10.9.4: dependencies: async: 3.2.6 - filelist: 1.0.4 + filelist: 1.0.6 picocolors: 1.1.1 js-stringify@1.0.2: {} @@ -1659,6 +2018,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + jstransformer@1.0.0: dependencies: is-promise: 2.2.2 @@ -1666,6 +2029,8 @@ snapshots: junk@1.0.3: {} + junk@3.1.0: {} + kind-of@6.0.3: {} kleur@4.1.5: {} @@ -1674,11 +2039,11 @@ snapshots: dependencies: uc.micro: 1.0.6 - linkify-it@5.0.0: + linkify-it@5.0.1: dependencies: uc.micro: 2.1.0 - liquidjs@10.24.0: + liquidjs@10.27.0: dependencies: commander: 10.0.1 @@ -1690,10 +2055,10 @@ snapshots: luxon@3.7.2: {} - markdown-it-anchor@9.2.0(@types/markdown-it@14.1.0)(markdown-it@14.1.0): + markdown-it-anchor@9.2.0(@types/markdown-it@14.1.0)(markdown-it@14.2.0): dependencies: '@types/markdown-it': 14.1.0 - markdown-it: 14.1.0 + markdown-it: 14.2.0 markdown-it-plantuml@1.4.1: {} @@ -1705,11 +2070,11 @@ snapshots: mdurl: 1.0.1 uc.micro: 1.0.6 - markdown-it@14.1.0: + markdown-it@14.2.0: dependencies: argparse: 2.0.1 entities: 4.5.0 - linkify-it: 5.0.0 + linkify-it: 5.0.1 mdurl: 2.0.0 punycode.js: 2.3.1 uc.micro: 2.1.0 @@ -1721,7 +2086,7 @@ snapshots: array-differ: 1.0.0 array-union: 1.0.2 arrify: 1.0.1 - minimatch: 3.1.2 + minimatch: 3.1.5 mdurl@1.0.1: {} @@ -1736,17 +2101,23 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 mime@3.0.0: {} - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.15 - minimatch@5.1.6: + minimatch@5.1.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.1 minimist@1.2.8: {} @@ -1754,13 +2125,17 @@ snapshots: dependencies: yallist: 4.0.0 + minipass@7.1.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 moo@0.5.2: {} - morphdom@2.7.7: {} + moo@0.5.3: {} + + morphdom@2.7.8: {} ms@2.0.0: {} @@ -1772,12 +2147,18 @@ snapshots: array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 - minimatch: 3.1.2 + minimatch: 3.1.5 mustache@4.2.0: {} neo-async@2.6.2: {} + node-retrieve-globals@6.0.1: + dependencies: + acorn: 8.16.0 + acorn-walk: 8.3.5 + esm-import-transformer: 3.0.5 + normalize-path@3.0.0: {} nth-check@2.1.1: @@ -1840,7 +2221,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} + + picomatch@4.0.4: {} pify@2.3.0: {} @@ -1848,6 +2231,10 @@ snapshots: dependencies: semver-compare: 1.0.0 + posthtml-match-helper@2.0.3(posthtml@0.16.7): + dependencies: + posthtml: 0.16.7 + posthtml-parser@0.11.0: dependencies: htmlparser2: 7.2.0 @@ -1886,7 +2273,7 @@ snapshots: js-stringify: 1.0.2 pug-runtime: 3.0.1 - pug-code-gen@3.0.3: + pug-code-gen@3.0.4: dependencies: constantinople: 4.0.1 doctypes: 1.1.0 @@ -1905,7 +2292,7 @@ snapshots: jstransformer: 1.0.0 pug-error: 2.1.0 pug-walk: 2.0.0 - resolve: 1.22.11 + resolve: 1.22.12 pug-lexer@5.0.1: dependencies: @@ -1936,9 +2323,9 @@ snapshots: pug-walk@2.0.0: {} - pug@3.0.3: + pug@3.0.4: dependencies: - pug-code-gen: 3.0.3 + pug-code-gen: 3.0.4 pug-filters: 4.0.0 pug-lexer: 5.0.1 pug-linker: 4.0.0 @@ -1951,9 +2338,11 @@ snapshots: queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 recursive-copy@2.0.14: dependencies: @@ -1967,9 +2356,10 @@ snapshots: rimraf: 2.7.1 slash: 1.0.0 - resolve@1.22.11: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -1992,7 +2382,25 @@ snapshots: semver-compare@1.0.0: {} - semver@7.7.3: {} + semver@7.8.3: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} shebang-command@2.0.0: dependencies: @@ -2002,12 +2410,18 @@ snapshots: slash@1.0.0: {} - slugify@1.6.6: {} + slash@3.0.0: {} + + slugify@1.6.9: {} source-map@0.6.1: {} sprintf-js@1.0.3: {} + ssri@11.0.0: + dependencies: + minipass: 7.1.3 + ssri@8.0.1: dependencies: minipass: 3.3.6 @@ -2020,10 +2434,17 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + token-stream@1.0.0: {} uc.micro@1.0.6: {} @@ -2033,10 +2454,12 @@ snapshots: uglify-js@3.19.3: optional: true - undici@7.18.2: {} + undici@7.27.2: {} unpipe@1.0.0: {} + urlpattern-polyfill@10.1.0: {} + void-elements@3.1.0: {} whatwg-encoding@3.1.1: @@ -2051,8 +2474,8 @@ snapshots: with@7.0.2: dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 assert-never: 1.4.0 babel-walk: 3.0.0-canary-5 @@ -2060,6 +2483,6 @@ snapshots: wrappy@1.0.2: {} - ws@8.19.0: {} + ws@8.21.0: {} yallist@4.0.0: {} diff --git a/docs/pnpm-workspace.yaml b/docs/pnpm-workspace.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/technical-guide/getting-started/docker.md b/docs/technical-guide/getting-started/docker.md index 53560235d3..a8c7b55ffa 100644 --- a/docs/technical-guide/getting-started/docker.md +++ b/docs/technical-guide/getting-started/docker.md @@ -206,6 +206,12 @@ server { proxy_pass http://localhost:9001/ws/notifications; } + location /mcp/ws { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_pass http://localhost:9001/mcp/ws; + } + # Proxy pass location / { proxy_set_header Host $http_host; diff --git a/exporter/package.json b/exporter/package.json index 9f75453b91..9ae777ceaf 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -4,29 +4,29 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.5.3+sha512.7ac1c919341c213a34dc0d02afb7143c5c26ac26ee8c4782deea821b8ac64d2134a081fd8941dae6e29bbb48f58dfc2b7fbceeccc07cb2f09d219d342a4969ed", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" }, "type": "module", "dependencies": { - "archiver": "7.0.1", + "archiver": "8.0.0", "cookies": "^0.9.1", - "date-fns": "^4.1.0", + "date-fns": "^4.4.0", "generic-pool": "^3.9.0", "inflation": "^2.1.0", - "ioredis": "^5.10.1", + "ioredis": "^5.11.1", "playwright": "^1.60.0", "raw-body": "^3.0.2", "source-map-support": "^0.5.21", - "svgo": "penpot/svgo#v3.1", - "undici": "^8.2.0", + "@penpot/svgo": "penpot/svgo#3.3.0", + "undici": "^8.4.1", "xml-js": "^1.6.11", "xregexp": "^5.1.2" }, "devDependencies": { - "ws": "^8.20.1" + "ws": "^8.21.0" }, "scripts": { "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", diff --git a/exporter/pnpm-lock.yaml b/exporter/pnpm-lock.yaml index 16082370ea..0bac244214 100644 --- a/exporter/pnpm-lock.yaml +++ b/exporter/pnpm-lock.yaml @@ -4,19 +4,27 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + lodash@<=4.17.23: ^4.17.24 + lodash@>=4.0.0 <=4.17.22: ^4.17.23 + lodash@>=4.0.0 <=4.17.23: ^4.17.24 + importers: .: dependencies: + '@penpot/svgo': + specifier: penpot/svgo#3.3.0 + version: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021 archiver: - specifier: 7.0.1 - version: 7.0.1 + specifier: 8.0.0 + version: 8.0.0 cookies: specifier: ^0.9.1 version: 0.9.1 date-fns: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.4.0 + version: 4.4.0 generic-pool: specifier: ^3.9.0 version: 3.9.0 @@ -24,8 +32,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 ioredis: - specifier: ^5.10.1 - version: 5.10.1 + specifier: ^5.11.1 + version: 5.11.1 playwright: specifier: ^1.60.0 version: 1.60.0 @@ -35,12 +43,9 @@ importers: source-map-support: specifier: ^0.5.21 version: 0.5.21 - svgo: - specifier: penpot/svgo#v3.1 - version: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180 undici: - specifier: ^8.2.0 - version: 8.2.0 + specifier: ^8.4.1 + version: 8.4.1 xml-js: specifier: ^1.6.11 version: 1.6.11 @@ -49,57 +54,29 @@ importers: version: 5.1.2 devDependencies: ws: - specifier: ^8.20.1 - version: 8.20.1 + specifier: ^8.21.0 + version: 8.21.0 packages: - '@babel/runtime-corejs3@7.28.4': - resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} engines: {node: '>=6.9.0'} - '@ioredis/commands@1.5.1': - resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} + '@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021': + resolution: {gitHosted: true, integrity: sha512-hG/pgVEWhmHEFMU+evGZkB5kHauff5Zo6ZO+Ro7HY0efsQTJft6svM4isH5jDISeSVrZ1CDGnhWBXuqkztsTWw==, tarball: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021} + version: 3.3.0 abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - archiver-utils@5.0.2: - resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} - engines: {node: '>= 14'} - - archiver@7.0.1: - resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} - engines: {node: '>= 14'} + archiver@8.0.0: + resolution: {integrity: sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==} + engines: {node: '>=18'} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -112,19 +89,20 @@ packages: react-native-b4a: optional: true - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + bare-events@2.9.1: + resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} peerDependencies: bare-abort-controller: '*' peerDependenciesMeta: bare-abort-controller: optional: true - bare-fs@4.7.1: - resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==} + bare-fs@4.7.2: + resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -136,8 +114,8 @@ packages: resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} engines: {bare: '>=1.14.0'} - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + bare-path@3.0.1: + resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} bare-stream@2.13.1: resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} @@ -153,17 +131,19 @@ packages: bare-events: optional: true - bare-url@2.4.3: - resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==} + bare-url@2.4.5: + resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boolbase@2.0.0: + resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} + engines: {node: '>=20.19.0'} - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} @@ -179,27 +159,20 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} engines: {node: '>=0.10.0'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - compress-commons@6.0.2: - resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} - engines: {node: '>= 14'} + compress-commons@7.0.1: + resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==} + engines: {node: '>=18'} cookies@0.9.1: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - core-js-pure@3.47.0: - resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==} + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -209,35 +182,32 @@ packages: engines: {node: '>=0.8'} hasBin: true - crc32-stream@6.0.0: - resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} - engines: {node: '>= 14'} + crc32-stream@7.0.1: + resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==} + engines: {node: '>=18'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-select@7.0.0: + resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==} + engines: {node: '>=20.19.0'} css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} + css-what@8.0.0: + resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==} + engines: {node: '>=20.19.0'} csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -256,31 +226,25 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} @@ -296,10 +260,6 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -309,20 +269,12 @@ packages: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - iconv-lite@0.7.1: - resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -335,65 +287,37 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ioredis@5.10.1: - resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} engines: {node: '>=12.22.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -402,19 +326,9 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + nth-check@3.0.1: + resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==} + engines: {node: '>=20.19.0'} playwright-core@1.60.0: resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} @@ -444,8 +358,9 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdir-glob@1.1.3: - resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdir-glob@3.0.0: + resolution: {integrity: sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==} + engines: {node: '>=18'} redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} @@ -464,24 +379,13 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.4.3: - resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -500,16 +404,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - streamx@2.25.0: - resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + streamx@2.27.0: + resolution: {integrity: sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==} string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -517,19 +413,6 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: - resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180} - version: 4.0.0 - engines: {node: '>=16.0.0'} - tar-stream@3.2.0: resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} @@ -547,8 +430,8 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - undici@8.2.0: - resolution: {integrity: sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==} + undici@8.4.1: + resolution: {integrity: sha512-RNHlB4fxZK0IrkhBsxhlbx7s8kFWwr7rzzOqj5nvZugw3ig3RsB7KW3zVlV0eu8POl+rx5d1hmL7rRg0z1owow==} engines: {node: '>=22.19.0'} unpipe@1.0.0: @@ -558,21 +441,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -590,65 +460,41 @@ packages: xregexp@5.1.2: resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==} - zip-stream@6.0.1: - resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} - engines: {node: '>= 14'} + zip-stream@7.0.5: + resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==} + engines: {node: '>=18'} snapshots: - '@babel/runtime-corejs3@7.28.4': + '@babel/runtime-corejs3@7.29.7': dependencies: - core-js-pure: 3.47.0 + core-js-pure: 3.49.0 - '@ioredis/commands@1.5.1': {} + '@ioredis/commands@1.10.0': {} - '@isaacs/cliui@8.0.2': + '@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@trysound/sax@0.2.0': {} + css-select: 7.0.0 + css-tree: 3.2.1 + csso: 5.0.5 + lodash: 4.18.1 + sax: 1.6.0 abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: + archiver@8.0.0: dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - archiver-utils@5.0.2: - dependencies: - glob: 10.5.0 - graceful-fs: 4.2.11 - is-stream: 2.0.1 - lazystream: 1.0.1 - lodash: 4.17.21 - normalize-path: 3.0.0 - readable-stream: 4.7.0 - - archiver@7.0.1: - dependencies: - archiver-utils: 5.0.2 async: 3.2.6 buffer-crc32: 1.0.0 + is-stream: 4.0.1 + lazystream: 1.0.1 + normalize-path: 3.0.0 readable-stream: 4.7.0 - readdir-glob: 1.1.3 + readdir-glob: 3.0.0 tar-stream: 3.2.0 - zip-stream: 6.0.1 + zip-stream: 7.0.5 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -658,16 +504,16 @@ snapshots: b4a@1.8.1: {} - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} - bare-events@2.8.2: {} + bare-events@2.9.1: {} - bare-fs@4.7.1: + bare-fs@4.7.2: dependencies: - bare-events: 2.8.2 - bare-path: 3.0.0 - bare-stream: 2.13.1(bare-events@2.8.2) - bare-url: 2.4.3 + bare-events: 2.9.1 + bare-path: 3.0.1 + bare-stream: 2.13.1(bare-events@2.9.1) + bare-url: 2.4.5 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller @@ -675,30 +521,30 @@ snapshots: bare-os@3.9.1: {} - bare-path@3.0.0: + bare-path@3.0.1: dependencies: bare-os: 3.9.1 - bare-stream@2.13.1(bare-events@2.8.2): + bare-stream@2.13.1(bare-events@2.9.1): dependencies: - streamx: 2.25.0 + streamx: 2.27.0 teex: 1.0.1 optionalDependencies: - bare-events: 2.8.2 + bare-events: 2.9.1 transitivePeerDependencies: - react-native-b4a - bare-url@2.4.3: + bare-url@2.4.5: dependencies: - bare-path: 3.0.0 + bare-path: 3.0.1 base64-js@1.5.1: {} - boolbase@1.0.0: {} + boolbase@2.0.0: {} - brace-expansion@2.1.0: + brace-expansion@5.0.6: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 buffer-crc32@1.0.0: {} @@ -711,19 +557,13 @@ snapshots: bytes@3.1.2: {} - cluster-key-slot@1.1.2: {} + cluster-key-slot@1.1.1: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - compress-commons@6.0.2: + compress-commons@7.0.1: dependencies: crc-32: 1.2.2 - crc32-stream: 6.0.0 - is-stream: 2.0.1 + crc32-stream: 7.0.1 + is-stream: 4.0.1 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -732,48 +572,42 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - core-js-pure@3.47.0: {} + core-js-pure@3.49.0: {} core-util-is@1.0.3: {} crc-32@1.2.2: {} - crc32-stream@6.0.0: + crc32-stream@7.0.1: dependencies: crc-32: 1.2.2 readable-stream: 4.7.0 - cross-spawn@7.0.6: + css-select@7.0.0: dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 + boolbase: 2.0.0 + css-what: 8.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + nth-check: 3.0.1 css-tree@2.2.1: dependencies: mdn-data: 2.0.28 source-map-js: 1.2.1 - css-tree@3.1.0: + css-tree@3.2.1: dependencies: - mdn-data: 2.12.2 + mdn-data: 2.27.1 source-map-js: 1.2.1 - css-what@6.2.2: {} + css-what@8.0.0: {} csso@5.0.5: dependencies: css-tree: 2.2.1 - date-fns@4.1.0: {} + date-fns@4.4.0: {} debug@4.4.3: dependencies: @@ -783,37 +617,31 @@ snapshots: depd@2.0.0: {} - dom-serializer@2.0.0: + dom-serializer@3.1.1: dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.0.0 - domelementtype@2.3.0: {} + domelementtype@3.0.0: {} - domhandler@5.0.3: + domhandler@6.0.1: dependencies: - domelementtype: 2.3.0 + domelementtype: 3.0.0 - domutils@3.2.2: + domutils@4.0.2: dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - entities@4.5.0: {} + entities@8.0.0: {} event-target-shim@5.0.1: {} events-universal@1.0.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.9.1 transitivePeerDependencies: - bare-abort-controller @@ -821,27 +649,11 @@ snapshots: fast-fifo@1.3.2: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fsevents@2.3.2: optional: true generic-pool@3.9.0: {} - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - graceful-fs@4.2.11: {} - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -850,7 +662,7 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - iconv-lite@0.7.1: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -860,34 +672,22 @@ snapshots: inherits@2.0.4: {} - ioredis@5.10.1: + ioredis@5.11.1: dependencies: - '@ioredis/commands': 1.5.1 - cluster-key-slot: 1.1.2 + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 debug: 4.4.3 denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 standard-as-callback: 2.1.0 transitivePeerDependencies: - supports-color - is-fullwidth-code-point@3.0.0: {} - - is-stream@2.0.1: {} + is-stream@4.0.1: {} isarray@1.0.0: {} - isexe@2.0.0: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - keygrip@1.1.0: dependencies: tsscmp: 1.0.6 @@ -896,44 +696,23 @@ snapshots: dependencies: readable-stream: 2.3.8 - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - - lodash@4.17.21: {} - - lru-cache@10.4.3: {} + lodash@4.18.1: {} mdn-data@2.0.28: {} - mdn-data@2.12.2: {} + mdn-data@2.27.1: {} - minimatch@5.1.9: + minimatch@10.2.5: dependencies: - brace-expansion: 2.1.0 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.1.0 - - minipass@7.1.3: {} + brace-expansion: 5.0.6 ms@2.1.3: {} normalize-path@3.0.0: {} - nth-check@2.1.1: + nth-check@3.0.1: dependencies: - boolbase: 1.0.0 - - package-json-from-dist@1.0.1: {} - - path-key@3.1.1: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 + boolbase: 2.0.0 playwright-core@1.60.0: {} @@ -951,7 +730,7 @@ snapshots: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.1 + iconv-lite: 0.7.2 unpipe: 1.0.0 readable-stream@2.3.8: @@ -972,9 +751,9 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 - readdir-glob@1.1.3: + readdir-glob@3.0.0: dependencies: - minimatch: 5.1.9 + minimatch: 10.2.5 redis-errors@1.2.0: {} @@ -988,18 +767,10 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.4.3: {} + sax@1.6.0: {} setprototypeof@1.2.0: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -1013,7 +784,7 @@ snapshots: statuses@2.0.2: {} - streamx@2.25.0: + streamx@2.27.0: dependencies: events-universal: 1.0.1 fast-fifo: 1.3.2 @@ -1022,18 +793,6 @@ snapshots: - bare-abort-controller - react-native-b4a - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -1042,28 +801,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: - dependencies: - '@trysound/sax': 0.2.0 - css-select: 5.2.2 - css-tree: 3.1.0 - csso: 5.0.5 - lodash: 4.17.21 - tar-stream@3.2.0: dependencies: b4a: 1.8.1 - bare-fs: 4.7.1 + bare-fs: 4.7.2 fast-fifo: 1.3.2 - streamx: 2.25.0 + streamx: 2.27.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -1071,7 +814,7 @@ snapshots: teex@1.0.1: dependencies: - streamx: 2.25.0 + streamx: 2.27.0 transitivePeerDependencies: - bare-abort-controller - react-native-b4a @@ -1086,40 +829,24 @@ snapshots: tsscmp@1.0.6: {} - undici@8.2.0: {} + undici@8.4.1: {} unpipe@1.0.0: {} util-deprecate@1.0.2: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - - ws@8.20.1: {} + ws@8.21.0: {} xml-js@1.6.11: dependencies: - sax: 1.4.3 + sax: 1.6.0 xregexp@5.1.2: dependencies: - '@babel/runtime-corejs3': 7.28.4 + '@babel/runtime-corejs3': 7.29.7 - zip-stream@6.0.1: + zip-stream@7.0.5: dependencies: - archiver-utils: 5.0.2 - compress-commons: 6.0.2 + compress-commons: 7.0.1 + normalize-path: 3.0.0 readable-stream: 4.7.0 diff --git a/exporter/pnpm-workspace.yaml b/exporter/pnpm-workspace.yaml index e69de29bb2..d71aa3ebaf 100644 --- a/exporter/pnpm-workspace.yaml +++ b/exporter/pnpm-workspace.yaml @@ -0,0 +1,9 @@ +allowBuilds: + core-js-pure: false +minimumReleaseAgeExclude: + - lodash@4.17.24 + - lodash@4.17.23 +overrides: + lodash@<=4.17.23: ^4.17.24 + lodash@>=4.0.0 <=4.17.22: ^4.17.23 + lodash@>=4.0.0 <=4.17.23: ^4.17.24 diff --git a/exporter/scripts/build b/exporter/scripts/build index 3daa2cf6f5..40eba8f44c 100755 --- a/exporter/scripts/build +++ b/exporter/scripts/build @@ -14,6 +14,7 @@ rm -rf target pnpm run build; cp pnpm-lock.yaml target/; +cp pnpm-workspace.yaml target/; cp package.json target/; touch target/pnpm-workspace.yaml; diff --git a/exporter/src/app/handlers/resources.cljs b/exporter/src/app/handlers/resources.cljs index 0c16de87d3..9b2f37d6cc 100644 --- a/exporter/src/app/handlers/resources.cljs +++ b/exporter/src/app/handlers/resources.cljs @@ -7,7 +7,7 @@ (ns app.handlers.resources "Temporal resources management." (:require - ["archiver$default" :as arc] + ["archiver" :as arc] ["node:fs" :as fs] ["node:fs/promises" :as fsp] ["node:path" :as path] @@ -41,7 +41,7 @@ (defn create-zip [& {:keys [resource on-complete on-progress on-error]}] - (let [^js zip (arc/create "zip") + (let [^js zip (new arc/ZipArchive) ^js out (fs/createWriteStream (:path resource)) on-complete (or on-complete (constantly nil)) progress (atom 0)] diff --git a/exporter/src/app/renderer/svg.cljs b/exporter/src/app/renderer/svg.cljs index ebac22349c..7a47e46fe0 100644 --- a/exporter/src/app/renderer/svg.cljs +++ b/exporter/src/app/renderer/svg.cljs @@ -6,7 +6,7 @@ (ns app.renderer.svg (:require - ["svgo" :as svgo] + ["@penpot/svgo" :as svgo] ["xml-js" :as xml] [app.browser :as bw] [app.common.data :as d] diff --git a/frontend/package.json b/frontend/package.json index 6863039995..f89183d33d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "browserslist": [ "defaults" ], @@ -17,8 +17,8 @@ "build:app:assets": "node ./scripts/build-app-assets.js", "build:storybook": "pnpm run build:storybook:assets && pnpm run build:storybook:cljs && storybook build", "build:storybook:assets": "node ./scripts/build-storybook-assets.js", - "build:wasm": "../render-wasm/build", "build:storybook:cljs": "clojure -M:dev:shadow-cljs compile storybook", + "build:wasm": "../render-wasm/build", "build:app:libs": "node ./scripts/build-libs.js", "build:app:main": "clojure -M:dev:shadow-cljs release main worker", "build:app:worker": "clojure -M:dev:shadow-cljs release worker", @@ -46,34 +46,35 @@ "clear:wasm": "cargo clean --manifest-path ../render-wasm/Cargo.toml", "watch": "exit 0", "watch:app": "pnpm run clear:shadow-cache && pnpm run clear:wasm && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"", - "watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"", + "watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 -h 0.0.0.0 --no-open\" \"node ./scripts/watch-storybook.js\"", "postinstall": "(cd ../plugins/libs/plugins-runtime; pnpm install; pnpm run build)" }, "devDependencies": { - "@penpot/draft-js": "workspace:./packages/draft-js", - "@penpot/mousetrap": "workspace:./packages/mousetrap", + "@penpot/draft-js": "link:packages/draft-js", + "@penpot/mousetrap": "link:packages/mousetrap", "@penpot/plugins-runtime": "link:../plugins/libs/plugins-runtime", - "@penpot/svgo": "penpot/svgo#v3.2", - "@penpot/text-editor": "workspace:./text-editor", - "@penpot/tokenscript": "workspace:./packages/tokenscript", - "@penpot/ui": "workspace:./packages/ui", - "@playwright/test": "1.60.0", - "@storybook/addon-docs": "10.3.5", - "@storybook/addon-themes": "10.3.5", - "@storybook/addon-vitest": "10.3.5", - "@storybook/react-vite": "10.3.5", - "@tokens-studio/sd-transforms": "1.2.11", - "@types/node": "^25.5.2", - "@vitest/browser": "4.1.3", - "@vitest/browser-playwright": "^4.1.3", - "@vitest/coverage-v8": "4.1.3", + "@penpot/svgo": "penpot/svgo#3.3.0", + "@penpot/text-editor": "link:text-editor", + "@penpot/tokenscript": "link:packages/tokenscript", + "@penpot/ua-parser": "penpot/ua-parser#1.0.0", + "@penpot/ui": "link:packages/ui", + "@playwright/test": "1.61.0", + "@storybook/addon-docs": "10.4.5", + "@storybook/addon-themes": "10.4.5", + "@storybook/addon-vitest": "10.4.5", + "@storybook/react-vite": "10.4.5", + "@tokens-studio/sd-transforms": "2.0.3", + "@types/node": "^25.9.3", + "@vitest/browser": "4.1.9", + "@vitest/browser-playwright": "^4.1.9", + "@vitest/coverage-v8": "4.1.9", "@zip.js/zip.js": "2.8.26", "autoprefixer": "^10.4.27", "compression": "^1.8.1", - "concurrently": "^9.2.1", - "date-fns": "^4.1.0", - "esbuild": "^0.28.0", - "eventsource-parser": "^3.0.8", + "concurrently": "^10.0.3", + "date-fns": "^4.4.0", + "esbuild": "^0.28.1", + "eventsource-parser": "^3.1.0", "express": "^5.1.0", "fancy-log": "^2.0.0", "getopts": "^2.3.0", @@ -84,48 +85,46 @@ "lodash": "^4.18.1", "lodash.debounce": "^4.0.8", "map-stream": "0.0.7", - "marked": "^17.0.5", + "marked": "^18.0.5", "mkdirp": "^3.0.1", "mustache": "^4.2.0", "nodemon": "^3.1.14", "npm-run-all": "^4.1.5", - "opentype.js": "^1.3.4", + "opentype.js": "^2.0.0", "p-limit": "^7.3.0", - "playwright": "1.60.0", - "postcss": "^8.5.8", + "playwright": "1.61.0", + "postcss": "^8.5.15", "postcss-clean": "^1.2.2", "postcss-modules": "^6.0.1", "postcss-scss": "^4.0.9", - "prettier": "3.8.1", + "prettier": "3.8.4", "pretty-time": "^1.1.0", "prop-types": "^15.8.1", "randomcolor": "^0.6.2", - "react": "19.2.4", - "react-dom": "19.2.4", - "react-error-boundary": "^6.1.1", + "react": "19.2.7", + "react-dom": "19.2.7", + "react-error-boundary": "^6.1.2", "react-virtualized": "^9.22.6", "rimraf": "^6.1.3", "rxjs": "8.0.0-alpha.14", - "sass": "^1.98.0", - "sass-embedded": "^1.98.0", - "sax": "^1.4.1", + "sass": "^1.100.0", + "sass-embedded": "^1.100.0", + "sax": "^1.6.0", "scheduler": "^0.27.0", "source-map-support": "^0.5.21", - "storybook": "10.3.5", - "style-dictionary": "5.0.0-rc.1", - "stylelint": "^17.4.0", + "storybook": "10.4.5", + "style-dictionary": "5.4.4", + "stylelint": "^17.13.0", "stylelint-config-standard-scss": "^17.0.0", - "stylelint-scss": "^7.0.0", + "stylelint-scss": "^7.2.0", "stylelint-use-logical-spec": "^5.0.1", "svg-sprite": "^2.0.4", "tdigest": "^0.1.2", "tinycolor2": "^1.6.0", "typescript": "^6.0.2", - "ua-parser-js": "2.0.9", - "vite": "^8.0.7", - "vitest": "^4.1.3", + "vite": "^8.0.16", + "vitest": "^4.1.9", "wait-on": "^9.0.4", - "wasm-pack": "^0.13.1", "watcher": "^2.3.1", "workerpool": "^10.0.1", "xregexp": "^5.1.2" diff --git a/frontend/packages/draft-js/package.json b/frontend/packages/draft-js/package.json index 64ffc18926..e89303a986 100644 --- a/frontend/packages/draft-js/package.json +++ b/frontend/packages/draft-js/package.json @@ -4,18 +4,18 @@ "description": "Penpot Draft-JS Wrapper", "main": "index.js", "type": "module", - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "author": "Andrey Antukh", "license": "MPL-2.0", "dependencies": { "draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0", - "immutable": "^5.1.4" + "immutable": "^5.1.6" }, "peerDependencies": { "react": ">=0.17.0", "react-dom": ">=0.17.0" }, "devDependencies": { - "esbuild": "^0.27.2" + "esbuild": "^0.28.1" } } diff --git a/frontend/packages/mousetrap/package.json b/frontend/packages/mousetrap/package.json index 46c44281b8..88c84ba0d2 100644 --- a/frontend/packages/mousetrap/package.json +++ b/frontend/packages/mousetrap/package.json @@ -4,7 +4,7 @@ "description": "Simple library for handling keyboard shortcuts", "main": "index.js", "type": "module", - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "author": "Craig Campbell", "license": "Apache-2.0 WITH LLVM-exception" } diff --git a/frontend/packages/tokenscript/package.json b/frontend/packages/tokenscript/package.json index a0d7cd8b53..ca1155e7e6 100644 --- a/frontend/packages/tokenscript/package.json +++ b/frontend/packages/tokenscript/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "type": "module", - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "author": "Andrey Antukh", "license": "MPL-2.0", "dependencies": { diff --git a/frontend/packages/ui/package.json b/frontend/packages/ui/package.json index 54d980acf2..c095047ab6 100644 --- a/frontend/packages/ui/package.json +++ b/frontend/packages/ui/package.json @@ -14,23 +14,23 @@ "build": "vite build" }, "devDependencies": { - "@babel/core": "^7.14.5", - "@babel/preset-react": "^7.14.5", - "@storybook/react": "10.3.5", - "@storybook/react-vite": "10.3.5", + "@babel/core": "^7.29.7", + "@babel/preset-react": "^7.29.7", + "@storybook/react": "10.4.5", + "@storybook/react-vite": "10.4.5", "@testing-library/dom": "10.4.1", "@testing-library/react": "16.3.2", - "@types/react": "^19.2.14", + "@types/react": "^19.2.17", "@types/react-dom": "^19.0.0", - "@vitejs/plugin-react": "^6.0.1", + "@vitejs/plugin-react": "^6.0.2", "babel-plugin-react-compiler": "^1.0.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "7.0.1", + "eslint-plugin-react-hooks": "7.1.1", "react-compiler-runtime": "^1.0.0", - "storybook": "10.3.5", - "vite-plugin-dts": "^4.5.4" + "storybook": "10.4.5", + "vite-plugin-dts": "^5.0.2" }, "peerDependencies": { "react": ">=19.2", diff --git a/frontend/playwright/data/viewer/get-file-fragment-rotated-board-stroke.json b/frontend/playwright/data/viewer/get-file-fragment-rotated-board-stroke.json new file mode 100644 index 0000000000..af47e655bb --- /dev/null +++ b/frontend/playwright/data/viewer/get-file-fragment-rotated-board-stroke.json @@ -0,0 +1,195 @@ +{ + "~:id": "~uaa5cc0bb-91ff-81b9-8004-77dfae2d9e7c", + "~:file-id": "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb1", + "~:created-at": "~m1717759268004", + "~:data": { + "~:options": {}, + "~:objects": { + "~u00000000-0000-0000-0000-000000000000": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:name": "Root Frame", + "~:width": 0.01, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0.01 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 0.01 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~u00000000-0000-0000-0000-000000000000", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 0, + "~:proportion": 1.0, + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 0.01, + "~:height": 0.01, + "~:x1": 0, + "~:y1": 0, + "~:x2": 0.01, + "~:y2": 0.01 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 0.01, + "~:flip-y": null, + "~:shapes": [ + "~ubc508673-9e3b-80bf-8004-77dfa30a2b13" + ] + } + }, + "~ubc508673-9e3b-80bf-8004-77dfa30a2b13": { + "~#shape": { + "~:y": 100, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 0.5735764363510460, + "~:b": 0.8191520442889918, + "~:c": -0.8191520442889918, + "~:d": 0.5735764363510460, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 55, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Board", + "~:width": 80, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 166.208, + "~:y": 92.816 + } + }, + { + "~#point": { + "~:x": 212.096, + "~:y": 158.352 + } + }, + { + "~#point": { + "~:x": 113.792, + "~:y": 227.184 + } + }, + { + "~#point": { + "~:x": 67.904, + "~:y": 161.648 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 0.5735764363510460, + "~:b": -0.8191520442889918, + "~:c": 0.8191520442889918, + "~:d": 0.5735764363510460, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~ubc508673-9e3b-80bf-8004-77dfa30a2b13", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [ + { + "~:stroke-color": "#FF0000", + "~:stroke-opacity": 1, + "~:stroke-style": "~:solid", + "~:stroke-alignment": "~:outer", + "~:stroke-width": 20 + } + ], + "~:x": 100, + "~:proportion": 1, + "~:selrect": { + "~#rect": { + "~:x": 100, + "~:y": 100, + "~:width": 80, + "~:height": 120, + "~:x1": 100, + "~:y1": 100, + "~:x2": 180, + "~:y2": 220 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 120, + "~:flip-y": null, + "~:shapes": [], + "~:show-content": true + } + } + }, + "~:id": "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb2", + "~:name": "Page 1" + } +} diff --git a/frontend/playwright/data/viewer/get-view-only-bundle-rotated-board-stroke.json b/frontend/playwright/data/viewer/get-view-only-bundle-rotated-board-stroke.json new file mode 100644 index 0000000000..3bd07bf48a --- /dev/null +++ b/frontend/playwright/data/viewer/get-view-only-bundle-rotated-board-stroke.json @@ -0,0 +1,86 @@ +{ + "~:users": [ + { + "~:id": "~u0515a066-e303-8169-8004-73eb4018f4e0", + "~:email": "leia@example.com", + "~:name": "Princesa Leia", + "~:fullname": "Princesa Leia", + "~:is-active": true + } + ], + "~:fonts": [], + "~:project": { + "~:id": "~u0515a066-e303-8169-8004-73eb401b5d55", + "~:name": "Drafts", + "~:team-id": "~u0515a066-e303-8169-8004-73eb401977a6" + }, + "~:share-links": [], + "~:libraries": [], + "~:file": { + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "Rotated Board Stroke Test", + "~:revn": 1, + "~:modified-at": "~m1717759268010", + "~:id": "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb1", + "~:is-shared": false, + "~:version": 48, + "~:project-id": "~u0515a066-e303-8169-8004-73eb401b5d55", + "~:created-at": "~m1717759250257", + "~:data": { + "~:id": "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb1", + "~:options": { + "~:components-v2": true + }, + "~:pages": [ + "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb2" + ], + "~:pages-index": { + "~uaa5cc0bb-91ff-81b9-8004-77df9cd3edb2": { + "~#penpot/pointer": [ + "~uaa5cc0bb-91ff-81b9-8004-77dfae2d9e7c", + { + "~:created-at": "~m1717759268024" + } + ] + } + } + } + }, + "~:team": { + "~:id": "~u0515a066-e303-8169-8004-73eb401977a6", + "~:created-at": "~m1717493865581", + "~:modified-at": "~m1717493865581", + "~:name": "Default", + "~:is-default": true, + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + } + }, + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true, + "~:in-team": true + } +} diff --git a/frontend/playwright/ui/pages/ViewerPage.js b/frontend/playwright/ui/pages/ViewerPage.js index 5aaf2e0c37..034b25e2f3 100644 --- a/frontend/playwright/ui/pages/ViewerPage.js +++ b/frontend/playwright/ui/pages/ViewerPage.js @@ -71,6 +71,21 @@ export class ViewerPage extends BaseWebSocketPage { ); } + async setupFileWithRotatedBoardStroke() { + await this.mockRPC( + /get\-view\-only\-bundle\?/, + "viewer/get-view-only-bundle-rotated-board-stroke.json", + ); + await this.mockRPC( + "get-comment-threads?file-id=*", + "workspace/get-comment-threads-empty.json", + ); + await this.mockRPC( + "get-file-fragment?file-id=*&fragment-id=*", + "viewer/get-file-fragment-rotated-board-stroke.json", + ); + } + async setupFileWithComments() { await this.mockRPC( /get\-view\-only\-bundle\?/, diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js index 5b6d8253a5..cc772aa3a3 100644 --- a/frontend/playwright/ui/specs/colorpicker.spec.js +++ b/frontend/playwright/ui/specs/colorpicker.spec.js @@ -318,4 +318,18 @@ test("Color picker color list", async ({ page }) => { await expect( colorpicker.getByRole("listitem", { name: "First color" }), ).toBeVisible(); + + //test show and hide color palette + const paletteToggle = workspacePage.page.getByRole('button', { name: 'Toggle color palette' }); + await paletteToggle.click(); + const paletteBar = workspacePage.page.getByRole('region', { name: 'Palette bar' }); + await expect(paletteBar).toBeVisible(); + + // Check that color palette is open by checking the presence of a color swatch in the palette + const paletteSwatch = paletteBar.getByRole('button', { name: 'first color' }); + await expect(paletteSwatch).toBeVisible(); + + // Close the color palette + await paletteToggle.click(); + await expect(paletteSwatch).not.toBeVisible(); }); diff --git a/frontend/playwright/ui/specs/multiseleccion.spec.js b/frontend/playwright/ui/specs/multiseleccion.spec.js index 3f7b351ce0..fc9ca391db 100644 --- a/frontend/playwright/ui/specs/multiseleccion.spec.js +++ b/frontend/playwright/ui/specs/multiseleccion.spec.js @@ -271,17 +271,15 @@ test("Multiselection of text and typographies", async ({ page }) => { await expect(textSection.getByText("Mixed assets")).toBeVisible(); // Select token typography text layer - // TODO: CHANGE WHEN TOKEN TYPOGRAPHY ROW IS READY await tokenTypographyTextLayerOne.click(); await expect(textSection).toBeVisible(); - await expect(textSection.getByText("Metrophobic")).toBeVisible(); + await expect(textSection.getByLabel('token-typo-one')).toBeVisible(); // Select two token typography text layer with different token typography - // TODO: CHANGE WHEN TOKEN TYPOGRAPHY ROW IS READY await tokenTypographyTextLayerTwo.click({ modifiers: ["Control"] }); await expect(textSection).toBeVisible(); await expect( - textSection.getByTitle("Font family").getByText("Mixed Font Families"), + textSection.getByText('Mixed tokens'), ).toBeVisible(); //Select plain text layer and typography text layer together diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index b570789284..7ee1238f64 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -186,11 +186,8 @@ test.describe("Tokens: Apply token", () => { await tokensSidebar.getByRole("button", { name: "Full" }).click(); - const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", { - name: "Font Size", - }); - await expect(fontSizeInput).toBeVisible(); - await expect(fontSizeInput).toHaveValue("100"); + const tokenRow = workspacePage.rightSidebar.getByLabel('Full'); + await expect(tokenRow).toBeVisible(); }); test("User adds shadow token with multiple shadows and applies it to shape", async ({ diff --git a/frontend/playwright/ui/specs/tokens/remapping.spec.js b/frontend/playwright/ui/specs/tokens/remapping.spec.js index 2123fed48e..703519e791 100644 --- a/frontend/playwright/ui/specs/tokens/remapping.spec.js +++ b/frontend/playwright/ui/specs/tokens/remapping.spec.js @@ -418,13 +418,9 @@ test.describe("Remapping a single token", () => { .filter({ hasText: "Some Text" }) .click(); - // Verify the shape shows the updated font size value (18) // This proves the remapping worked and the value update propagated through the reference - const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", { - name: "Font Size", - }); - await expect(fontSizeInput).toBeVisible(); - await expect(fontSizeInput).toHaveValue("18"); + const tokenPillSidebar = workspacePage.rightSidebar.getByLabel('paragraph-style') + await expect(tokenPillSidebar).toBeVisible(); }); }); diff --git a/frontend/playwright/ui/specs/viewer-inspect-exports.spec.js b/frontend/playwright/ui/specs/viewer-inspect-exports.spec.js new file mode 100644 index 0000000000..3e99476fe5 --- /dev/null +++ b/frontend/playwright/ui/specs/viewer-inspect-exports.spec.js @@ -0,0 +1,49 @@ +import { test, expect } from "@playwright/test"; +import { ViewerPage } from "../pages/ViewerPage"; + +test.beforeEach(async ({ page }) => { + await ViewerPage.init(page); +}); + +const multipleBoardsFileId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0"; +const multipleBoardsPageId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3"; + +test("[View mode] Export presets are preserved when navigating between boards in inspect mode", async ({ + page, +}) => { + const viewer = new ViewerPage(page); + await viewer.setupLoggedInUser(); + await viewer.setupFileWithMultipleBoards(); + + await viewer.goToViewer({ + fileId: multipleBoardsFileId, + pageId: multipleBoardsPageId, + }); + + // Enter inspect (code) mode + await viewer.showCode(); + + // Wait for the inspect panel to load + await page.waitForSelector(".main_ui_inspect_exports__add-export"); + + // Add an export preset via the "+" button in the Export section + const addExportButton = page.locator(".main_ui_inspect_exports__add-export"); + await addExportButton.click(); + + // Verify the "Export 1 element" button appears, confirming the preset was added + const exportButton = page.getByRole("button", { name: "Export 1 element" }); + await expect(exportButton).toBeVisible(); + + // Navigate to another board + const nextButton = page.getByRole("button", { name: "Next" }); + await nextButton.click(); + await expect(page).toHaveURL(/&index=1/); + + // Navigate back to the first board + const prevButton = page.locator(".main_ui_viewer__viewer-go-prev"); + await prevButton.click(); + await expect(page).toHaveURL(/&index=0/); + + // Export preset should still be visible after returning to the first board + await expect(exportButton).toBeVisible(); +}); diff --git a/frontend/playwright/ui/specs/viewer-rotated-board-stroke.spec.js b/frontend/playwright/ui/specs/viewer-rotated-board-stroke.spec.js new file mode 100644 index 0000000000..05fda576e8 --- /dev/null +++ b/frontend/playwright/ui/specs/viewer-rotated-board-stroke.spec.js @@ -0,0 +1,47 @@ +import { test, expect } from "@playwright/test"; +import { ViewerPage } from "../pages/ViewerPage"; + +// Issue 8257: outer stroke of a rotated board is cropped in View Mode. +// The SVG viewport must be large enough to contain the stroke of a rotated board. +// A 55° rotated board (80×120) with a 20px outer stroke has a rotated bounding box +// of ~144×134px. The viewport must be at least ~202×192px (bbox + stroke margin). + +test.beforeEach(async ({ page }) => { + await ViewerPage.init(page); +}); + +const rotatedBoardFileId = "aa5cc0bb-91ff-81b9-8004-77df9cd3edb1"; +const rotatedBoardPageId = "aa5cc0bb-91ff-81b9-8004-77df9cd3edb2"; + +test("Viewer shows full outer stroke of a rotated board without clipping", async ({ + page, +}) => { + const viewer = new ViewerPage(page); + await viewer.setupLoggedInUser(); + await viewer.setupFileWithRotatedBoardStroke(); + + await viewer.goToViewer({ + fileId: rotatedBoardFileId, + pageId: rotatedBoardPageId, + }); + + // Wait for the viewer SVG to be rendered + const svg = page.locator("svg[class*='not-fixed']").first(); + await expect(svg).toBeVisible(); + + // The SVG viewBox must be large enough to contain the rotated board plus its + // 20px outer stroke. For a 55° rotated board (80×120): + // - The axis-aligned bounding box of the rotated frame is ~144×134px + // - The outer stroke (20px) adds sqrt(2)*20 ≈ 29px margin on each side + // - So the viewport must be at least ~202×192px + // + // Before the fix, the viewer used the unrotated selrect (80×120) as the viewport, + // causing the stroke to be heavily clipped. + const viewBox = await svg.getAttribute("viewBox"); + const [, , vbWidth, vbHeight] = viewBox.split(" ").map(Number); + + // The unrotated selrect is 80×120. If the viewport is close to those dimensions, + // the stroke is being clipped (bug). The fixed viewport should be much larger. + expect(vbWidth).toBeGreaterThan(150); + expect(vbHeight).toBeGreaterThan(150); +}); diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 04852ba1b0..a9a2d18d4f 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,90 +4,105 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + ajv@>=7.0.0-alpha.0 <8.18.0: ^8.18.0 + immutable@<3.8.3: ^3.8.3 + lodash@<=4.17.23: ^4.17.24 + lodash@>=4.0.0 <=4.17.23: ^4.17.24 + minimatch@>=10.0.0 <10.2.1: ^10.2.1 + minimatch@>=10.0.0 <10.2.3: ^10.2.3 + minimatch@>=9.0.0 <9.0.6: ^9.0.6 + minimatch@>=9.0.0 <9.0.7: ^9.0.7 + postcss@<7.0.36: ^7.0.36 + postcss@<8.4.31: ^8.4.31 + postcss@<8.5.10: ^8.5.10 + yaml@>=2.0.0 <2.8.3: ^2.8.3 + patchedDependencies: - '@zip.js/zip.js@2.8.26': - hash: 7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95 - path: patches/@zip.js__zip.js@2.8.26.patch + '@zip.js/zip.js@2.8.26': 7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95 importers: .: devDependencies: '@penpot/draft-js': - specifier: workspace:./packages/draft-js + specifier: link:packages/draft-js version: link:packages/draft-js '@penpot/mousetrap': - specifier: workspace:./packages/mousetrap + specifier: link:packages/mousetrap version: link:packages/mousetrap '@penpot/plugins-runtime': specifier: link:../plugins/libs/plugins-runtime version: link:../plugins/libs/plugins-runtime '@penpot/svgo': - specifier: penpot/svgo#v3.2 - version: svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b + specifier: penpot/svgo#3.3.0 + version: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021 '@penpot/text-editor': - specifier: workspace:./text-editor + specifier: link:text-editor version: link:text-editor '@penpot/tokenscript': - specifier: workspace:./packages/tokenscript + specifier: link:packages/tokenscript version: link:packages/tokenscript + '@penpot/ua-parser': + specifier: penpot/ua-parser#1.0.0 + version: https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4 '@penpot/ui': - specifier: workspace:./packages/ui + specifier: link:packages/ui version: link:packages/ui '@playwright/test': - specifier: 1.60.0 - version: 1.60.0 + specifier: 1.61.0 + version: 1.61.0 '@storybook/addon-docs': - specifier: 10.3.5 - version: 10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: 10.4.5 + version: 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) '@storybook/addon-themes': - specifier: 10.3.5 - version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + specifier: 10.4.5 + version: 10.4.5(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) '@storybook/addon-vitest': - specifier: 10.3.5 - version: 10.3.5(@vitest/browser-playwright@4.1.6)(@vitest/browser@4.1.3)(@vitest/runner@4.1.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.6) + specifier: 10.4.5 + version: 10.4.5(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(@vitest/runner@4.1.9)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.9) '@storybook/react-vite': - specifier: 10.3.5 - version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: 10.4.5 + version: 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) '@tokens-studio/sd-transforms': - specifier: 1.2.11 - version: 1.2.11(style-dictionary@5.0.0-rc.1(tslib@2.8.1)) + specifier: 2.0.3 + version: 2.0.3(style-dictionary@5.4.4(tslib@2.8.1)) '@types/node': - specifier: ^25.5.2 - version: 25.7.0 + specifier: ^25.9.3 + version: 25.9.3 '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + specifier: 4.1.9 + version: 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) '@vitest/browser-playwright': - specifier: ^4.1.3 - version: 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + specifier: ^4.1.9 + version: 4.1.9(playwright@1.61.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) '@vitest/coverage-v8': - specifier: 4.1.3 - version: 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6) + specifier: 4.1.9 + version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) '@zip.js/zip.js': specifier: 2.8.26 version: 2.8.26(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95) autoprefixer: specifier: ^10.4.27 - version: 10.5.0(postcss@8.5.14) + version: 10.5.0(postcss@8.5.15) compression: specifier: ^1.8.1 version: 1.8.1 concurrently: - specifier: ^9.2.1 - version: 9.2.1 + specifier: ^10.0.3 + version: 10.0.3 date-fns: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.4.0 + version: 4.4.0 esbuild: - specifier: ^0.28.0 - version: 0.28.0 + specifier: ^0.28.1 + version: 0.28.1 eventsource-parser: - specifier: ^3.0.8 - version: 3.0.8 + specifier: ^3.1.0 + version: 3.1.0 express: specifier: ^5.1.0 - version: 5.2.1 + version: 5.2.1(supports-color@5.5.0) fancy-log: specifier: ^2.0.0 version: 2.0.0 @@ -105,7 +120,7 @@ importers: version: 1.15.4 jsdom: specifier: ^29.0.2 - version: 29.1.1(canvas@3.2.1) + version: 29.1.1(canvas@3.2.3) lodash: specifier: ^4.18.1 version: 4.18.1 @@ -116,8 +131,8 @@ importers: specifier: 0.0.7 version: 0.0.7 marked: - specifier: ^17.0.5 - version: 17.0.6 + specifier: ^18.0.5 + version: 18.0.5 mkdirp: specifier: ^3.0.1 version: 3.0.1 @@ -131,29 +146,29 @@ importers: specifier: ^4.1.5 version: 4.1.5 opentype.js: - specifier: ^1.3.4 - version: 1.3.4 + specifier: ^2.0.0 + version: 2.0.0 p-limit: specifier: ^7.3.0 version: 7.3.0 playwright: - specifier: 1.60.0 - version: 1.60.0 + specifier: 1.61.0 + version: 1.61.0 postcss: - specifier: ^8.5.8 - version: 8.5.14 + specifier: ^8.5.15 + version: 8.5.15 postcss-clean: specifier: ^1.2.2 version: 1.2.2 postcss-modules: specifier: ^6.0.1 - version: 6.0.1(postcss@8.5.14) + version: 6.0.1(postcss@8.5.15) postcss-scss: specifier: ^4.0.9 - version: 4.0.9(postcss@8.5.14) + version: 4.0.9(postcss@8.5.15) prettier: - specifier: 3.8.1 - version: 3.8.1 + specifier: 3.8.4 + version: 3.8.4 pretty-time: specifier: ^1.1.0 version: 1.1.0 @@ -164,17 +179,17 @@ importers: specifier: ^0.6.2 version: 0.6.2 react: - specifier: 19.2.4 - version: 19.2.4 + specifier: 19.2.7 + version: 19.2.7 react-dom: - specifier: 19.2.4 - version: 19.2.4(react@19.2.4) + specifier: 19.2.7 + version: 19.2.7(react@19.2.7) react-error-boundary: - specifier: ^6.1.1 - version: 6.1.1(react@19.2.4) + specifier: ^6.1.2 + version: 6.1.2(react@19.2.7) react-virtualized: specifier: ^9.22.6 - version: 9.22.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 9.22.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) rimraf: specifier: ^6.1.3 version: 6.1.3 @@ -182,14 +197,14 @@ importers: specifier: 8.0.0-alpha.14 version: 8.0.0-alpha.14 sass: - specifier: ^1.98.0 - version: 1.99.0 + specifier: ^1.100.0 + version: 1.101.0 sass-embedded: - specifier: ^1.98.0 - version: 1.99.0 + specifier: ^1.100.0 + version: 1.100.0 sax: - specifier: ^1.4.1 - version: 1.4.4 + specifier: ^1.6.0 + version: 1.6.0 scheduler: specifier: ^0.27.0 version: 0.27.0 @@ -197,23 +212,23 @@ importers: specifier: ^0.5.21 version: 0.5.21 storybook: - specifier: 10.3.5 - version: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 10.4.5 + version: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) style-dictionary: - specifier: 5.0.0-rc.1 - version: 5.0.0-rc.1(tslib@2.8.1) + specifier: 5.4.4 + version: 5.4.4(tslib@2.8.1) stylelint: - specifier: ^17.4.0 - version: 17.11.0(typescript@6.0.3) + specifier: ^17.13.0 + version: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) stylelint-config-standard-scss: specifier: ^17.0.0 - version: 17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@6.0.3)) + version: 17.0.0(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) stylelint-scss: - specifier: ^7.0.0 - version: 7.1.1(stylelint@17.11.0(typescript@6.0.3)) + specifier: ^7.2.0 + version: 7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) stylelint-use-logical-spec: specifier: ^5.0.1 - version: 5.0.1(stylelint@17.11.0(typescript@6.0.3)) + version: 5.0.1(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) svg-sprite: specifier: ^2.0.4 version: 2.0.4 @@ -226,21 +241,15 @@ importers: typescript: specifier: ^6.0.2 version: 6.0.3 - ua-parser-js: - specifier: 2.0.9 - version: 2.0.9 vite: - specifier: ^8.0.7 - version: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + specifier: ^8.0.16 + version: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) vitest: - specifier: ^4.1.3 - version: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: ^4.1.9 + version: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) wait-on: specifier: ^9.0.4 - version: 9.0.10 - wasm-pack: - specifier: ^0.13.1 - version: 0.13.1 + version: 9.0.10(supports-color@5.5.0) watcher: specifier: ^2.3.1 version: 2.3.1 @@ -255,20 +264,20 @@ importers: dependencies: draft-js: specifier: penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0 - version: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) immutable: - specifier: ^5.1.4 - version: 5.1.4 + specifier: ^5.1.6 + version: 5.1.6 react: specifier: '>=0.17.0' - version: 19.2.3 + version: 19.2.7 react-dom: specifier: '>=0.17.0' - version: 19.2.3(react@19.2.3) + version: 19.2.7(react@19.2.7) devDependencies: esbuild: - specifier: ^0.27.2 - version: 0.27.3 + specifier: ^0.28.1 + version: 0.28.1 packages/mousetrap: {} @@ -282,113 +291,106 @@ importers: dependencies: react: specifier: '>=19.2' - version: 19.2.3 + version: 19.2.7 react-dom: specifier: '>=19.2' - version: 19.2.3(react@19.2.3) + version: 19.2.7(react@19.2.7) devDependencies: '@babel/core': - specifier: ^7.14.5 - version: 7.29.0 + specifier: ^7.29.7 + version: 7.29.7(supports-color@5.5.0) '@babel/preset-react': - specifier: ^7.14.5 - version: 7.28.5(@babel/core@7.29.0) + specifier: ^7.29.7 + version: 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) '@storybook/react': - specifier: 10.3.5 - version: 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3) + specifier: 10.4.5 + version: 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) '@storybook/react-vite': - specifier: 10.3.5 - version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: 10.4.5 + version: 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/react': - specifier: ^19.2.14 - version: 19.2.14 + specifier: ^19.2.17 + version: 19.2.17 '@types/react-dom': specifier: ^19.0.0 - version: 19.2.3(@types/react@19.2.14) + version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: ^6.0.2 + version: 6.0.2(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(eslint@9.39.2) + version: 2.32.0(eslint@9.39.4) eslint-plugin-jsx-a11y: specifier: 6.10.2 - version: 6.10.2(eslint@9.39.2) + version: 6.10.2(eslint@9.39.4) eslint-plugin-react: specifier: 7.37.5 - version: 7.37.5(eslint@9.39.2) + version: 7.37.5(eslint@9.39.4) eslint-plugin-react-hooks: - specifier: 7.0.1 - version: 7.0.1(eslint@9.39.2) + specifier: 7.1.1 + version: 7.1.1(eslint@9.39.4) react-compiler-runtime: specifier: ^1.0.0 - version: 1.0.0(react@19.2.3) + version: 1.0.0(react@19.2.7) storybook: - specifier: 10.3.5 - version: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 10.4.5 + version: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) vite-plugin-dts: - specifier: ^4.5.4 - version: 4.5.4(@types/node@25.7.0)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + specifier: ^5.0.2 + version: 5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) text-editor: devDependencies: '@playwright/test': - specifier: ^1.45.1 - version: 1.58.0 + specifier: ^1.61.0 + version: 1.61.0 '@types/node': - specifier: ^25.0.3 - version: 25.2.1 + specifier: ^25.9.2 + version: 25.9.3 '@vitest/browser': - specifier: ^1.6.0 - version: 1.6.1(playwright@1.58.0)(vitest@1.6.1) + specifier: ^4.1.9 + version: 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) '@vitest/coverage-v8': - specifier: ^1.6.0 - version: 1.6.1(vitest@1.6.1) + specifier: ^4.1.9 + version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) '@vitest/ui': - specifier: ^1.6.0 - version: 1.6.1(vitest@1.6.1) + specifier: ^4.1.9 + version: 4.1.9(vitest@4.1.9) canvas: - specifier: ^3.2.1 - version: 3.2.1 + specifier: ^3.2.3 + version: 3.2.3 esbuild: - specifier: ^0.27.2 - version: 0.27.3 + specifier: ^0.28.0 + version: 0.28.1 jsdom: - specifier: ^27.4.0 - version: 27.4.0(canvas@3.2.1) + specifier: ^29.1.1 + version: 29.1.1(canvas@3.2.3) playwright: - specifier: ^1.45.1 - version: 1.58.0 + specifier: ^1.61.0 + version: 1.61.0 prettier: - specifier: ^3.7.4 - version: 3.8.1 + specifier: ^3.8.4 + version: 3.8.4 vite: - specifier: ^5.3.1 - version: 5.4.21(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + specifier: ^8.0.16 + version: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) vitest: - specifier: ^1.6.0 - version: 1.6.1(@types/node@25.2.1)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + specifier: ^4.1.9 + version: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) packages: - '@acemir/cssom@0.9.31': - resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} '@ark/schema@0.56.0': resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} @@ -396,16 +398,10 @@ packages: '@ark/util@0.56.0': resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} - '@asamuzakjp/css-color@4.1.2': - resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@asamuzakjp/dom-selector@6.7.8': - resolution: {integrity: sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==} - '@asamuzakjp/dom-selector@7.1.1': resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -417,128 +413,125 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-display-name@7.28.0': - resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + '@babel/plugin-transform-react-display-name@7.29.7': + resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-development@7.27.1': - resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + '@babel/plugin-transform-react-jsx-development@7.29.7': + resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.28.6': - resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-pure-annotations@7.27.1': - resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + '@babel/plugin-transform-react-pure-annotations@7.29.7': + resolution: {integrity: sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-react@7.28.5': - resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} + '@babel/preset-react@7.29.7': + resolution: {integrity: sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime-corejs3@7.29.0': - resolution: {integrity: sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==} + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -550,14 +543,14 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@bufbuild/protobuf@2.11.0': - resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} + '@bufbuild/protobuf@2.12.0': + resolution: {integrity: sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==} - '@bundled-es-modules/deepmerge@4.3.1': - resolution: {integrity: sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==} + '@bundled-es-modules/deepmerge@4.3.2': + resolution: {integrity: sha512-q8doe7ndrY2IolUOFIn0A0++JBX38pMhN7kFhTF4cnjIcILf6X6H2yWczInyv8ZFdR0lrE8088X8XS5efxXz8A==} - '@bundled-es-modules/glob@10.4.2': - resolution: {integrity: sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==} + '@bundled-es-modules/glob@13.0.6': + resolution: {integrity: sha512-x9nR2e1pt8LF0yLPC6yz/aUoiN7qJJwZ1znLxIXCxGyH+8BI+yO/sklBdn1+QbUyWXQBM+CjfZz3IhqtgIoDVg==} '@bundled-es-modules/memfs@4.17.0': resolution: {integrity: sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==} @@ -565,8 +558,8 @@ packages: '@bundled-es-modules/postcss-calc-ast-parser@0.1.6': resolution: {integrity: sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==} - '@cacheable/memory@2.0.8': - resolution: {integrity: sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==} + '@cacheable/memory@2.0.9': + resolution: {integrity: sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==} '@cacheable/utils@2.4.1': resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} @@ -575,21 +568,10 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - '@csstools/color-helpers@6.0.1': - resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} - engines: {node: '>=20.19.0'} - '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.0.0': - resolution: {integrity: sha512-q4d82GTl8BIlh/dTnVsWmxnbWJeb3kiU8eUH71UxlxnS+WIaALmtzTL8gR15PkYOexMQYVk0CO4qIG93C1IvPA==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-calc@3.2.1': resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} engines: {node: '>=20.19.0'} @@ -597,15 +579,8 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.0.1': - resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-color-parser@4.1.1': - resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + '@csstools/css-color-parser@4.1.3': + resolution: {integrity: sha512-DOgvIPkikIOixQRlD4YF31VN6fLLUTdrzhfRbis8vm0kMTgIbEPX0Ip/YX9fOeV9iywAS4sUUbTclpan7yYP8Q==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -617,11 +592,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.26': - resolution: {integrity: sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==} - - '@csstools/css-syntax-patches-for-csstree@1.1.4': - resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -657,614 +629,326 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.4': - resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.4': - resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.4': - resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.4': - resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.4': - resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.4': - resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.4': - resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.4': - resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.4': - resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.4': - resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.4': - resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.4': - resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.4': - resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.4': - resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.4': - resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.4': - resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.4': - resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.4': - resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.4': - resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.4': - resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.4': - resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.4': - resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.4': - resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.4': - resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.4': - resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.4': - resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1295,8 +979,8 @@ packages: resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -1307,17 +991,8 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@exodus/bytes@1.11.0': - resolution: {integrity: sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - peerDependencies: - '@noble/hashes': ^1.8.0 || ^2.0.0 - peerDependenciesMeta: - '@noble/hashes': - optional: true - - '@exodus/bytes@1.15.0': - resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@noble/hashes': ^1.8.0 || ^2.0.0 @@ -1338,8 +1013,8 @@ packages: '@hapi/pinpoint@2.0.1': resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} - '@hapi/tlds@1.1.4': - resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==} + '@hapi/tlds@1.1.7': + resolution: {integrity: sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==} engines: {node: '>=14.0.0'} '@hapi/topo@6.0.2': @@ -1365,26 +1040,10 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.1': - resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} peerDependencies: @@ -1416,8 +1075,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/base64@17.65.0': - resolution: {integrity: sha512-Xrh7Fm/M0QAYpekSgmskdZYnFdSGnsxJ/tHaolA4bNwWdG9i65S8m83Meh7FOxyJyQAdo4d4J97NOomBLEfkDQ==} + '@jsonjoy.com/base64@17.67.0': + resolution: {integrity: sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1428,8 +1087,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/buffers@17.65.0': - resolution: {integrity: sha512-eBrIXd0/Ld3p9lpDDlMaMn6IEfWqtHMD+z61u0JrIiPzsV1r7m6xDZFRxJyvIFTEO+SWdYF9EiQbXZGd8BzPfA==} + '@jsonjoy.com/buffers@17.67.0': + resolution: {integrity: sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1440,56 +1099,56 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/codegen@17.65.0': - resolution: {integrity: sha512-7MXcRYe7n3BG+fo3jicvjB0+6ypl2Y/bQp79Sp7KeSiiCgLqw4Oled6chVv07/xLVTdo3qa1CD0VCCnPaw+RGA==} + '@jsonjoy.com/codegen@17.67.0': + resolution: {integrity: sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-core@4.56.10': - resolution: {integrity: sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==} + '@jsonjoy.com/fs-core@4.57.7': + resolution: {integrity: sha512-GDKuYHjP7vAI1kjBo73V+STKr9XIMZknW/xirpRW/EcShX0IKSev/ALafeRfC8Q331nodrXUFu04PugPB0MAhw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-fsa@4.56.10': - resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} + '@jsonjoy.com/fs-fsa@4.57.7': + resolution: {integrity: sha512-1rWsah2nZtRbNeP+c61QcfGfVrJXBmBD0Hm7Akvv4C9MKEasXnbiOS//iH3T3HwUSSBATGrfSp0Xi8nlNhATeQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.56.10': - resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} + '@jsonjoy.com/fs-node-builtins@4.57.7': + resolution: {integrity: sha512-LWqfY1m+uAosjwM1RrKhMkUnP9jcq1RUczHsNO779ovm1E9v8I/pmj04eBAcoBjhC7ltcPbNFGyRJ5JqSJ7Jdg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.56.10': - resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} + '@jsonjoy.com/fs-node-to-fsa@4.57.7': + resolution: {integrity: sha512-9T0zC9LKcAWXDoTLRdLMoJ0seOvJ5bgDKq1tSBoQAFQpPDstQUeV1Oe7PLypdu7F2D3ddRstmwgeNUEN/VaZ4Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-utils@4.56.10': - resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} + '@jsonjoy.com/fs-node-utils@4.57.7': + resolution: {integrity: sha512-jjWSDOsfcog2cZnUCwX5AHmlIq6b6wx5Pz/2LAcNjJ62Rajwg89Fy7ubN+lDHew0/1reLDa9Z5urybYadhh37g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node@4.56.10': - resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} + '@jsonjoy.com/fs-node@4.57.7': + resolution: {integrity: sha512-xhnyeyEVTiIOibFvda/5n89nChMLCPKHHM2WQ+GGDf6+U/IrQBW3Qx6x+Uq1bkDbxBkybLOdIGoBtVBrE8Nngg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-print@4.56.10': - resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} + '@jsonjoy.com/fs-print@4.57.7': + resolution: {integrity: sha512-mFM4P4Gjq0QQHkLnXzPYPEMFrAoe6a5Myedgb6+CmL+nGd3MKvTxYPuD7N1dLIH9RBy1fLdzxd80qvuK8xrx3Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.56.10': - resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} + '@jsonjoy.com/fs-snapshot@4.57.7': + resolution: {integrity: sha512-1GS3+plfm2giB3PqokiqyydyqYTPLcCQIKSkp0TdMNRh3KVk7rqRM6U785FLlVRG7XLmkc0KWr215OY+22K3QA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1500,8 +1159,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/json-pack@17.65.0': - resolution: {integrity: sha512-e0SG/6qUCnVhHa0rjDJHgnXnbsacooHVqQHxspjvlYQSkHm+66wkHw6Gql+3u/WxI/b1VsOdUi0M+fOtkgKGdQ==} + '@jsonjoy.com/json-pack@17.67.0': + resolution: {integrity: sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1512,8 +1171,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/json-pointer@17.65.0': - resolution: {integrity: sha512-uhTe+XhlIZpWOxgPcnO+iSCDgKKBpwkDVTyYiXX9VayGV8HSFVJM67M6pUE71zdnXF1W0Da21AvnhlmdwYPpow==} + '@jsonjoy.com/json-pointer@17.67.0': + resolution: {integrity: sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1524,8 +1183,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/util@17.65.0': - resolution: {integrity: sha512-cWiEHZccQORf96q2y6zU3wDeIVPeidmGqd9cNKJRYoVHTV0S1eHPy5JTbHpMnGfDvtvujQwQozOqgO9ABu6h0w==} + '@jsonjoy.com/util@17.67.0': + resolution: {integrity: sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1545,21 +1204,8 @@ packages: '@types/react': '>=16' react: '>=16' - '@microsoft/api-extractor-model@7.32.2': - resolution: {integrity: sha512-Ussc25rAalc+4JJs9HNQE7TuO9y6jpYQX9nWD1DhqUzYPBr3Lr7O9intf+ZY8kD5HnIqeIRJX7ccCT0QyBy2Ww==} - - '@microsoft/api-extractor@7.56.2': - resolution: {integrity: sha512-d94f7S+H43jDxY/YIyp5UOE9N12HZmEPP5Ct96us+fw1FVKBoy4itTopdVM1VrcpduuA7fK/t31CgA2jM8AqSA==} - hasBin: true - - '@microsoft/tsdoc-config@0.18.0': - resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} - - '@microsoft/tsdoc@0.16.0': - resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} - - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 @@ -1579,8 +1225,241 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} - '@oxc-project/types@0.129.0': - resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + resolution: {integrity: sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.20.0': + resolution: {integrity: sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + resolution: {integrity: sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.20.0': + resolution: {integrity: sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + resolution: {integrity: sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + resolution: {integrity: sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + resolution: {integrity: sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + resolution: {integrity: sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + resolution: {integrity: sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + resolution: {integrity: sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + resolution: {integrity: sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + resolution: {integrity: sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + resolution: {integrity: sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + resolution: {integrity: sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + resolution: {integrity: sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + resolution: {integrity: sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': + resolution: {integrity: sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + resolution: {integrity: sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + resolution: {integrity: sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==} + cpu: [x64] + os: [win32] '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} @@ -1670,17 +1549,20 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021': + resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021} + version: 3.3.0 + + '@penpot/ua-parser@https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4': + resolution: {gitHosted: true, integrity: sha512-BxcjiWGtCbGBT+dsOnEODk1jASZLNYp27BuGQaJR7fxU4gLws3251r90Sp9seubcpRhGrfRdsA5WU0ExRdPOgg==, tarball: https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4} + version: 1.0.0 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.58.0': - resolution: {integrity: sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==} - engines: {node: '>=18'} - hasBin: true - - '@playwright/test@1.60.0': - resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + '@playwright/test@1.61.0': + resolution: {integrity: sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==} engines: {node: '>=18'} hasBin: true @@ -1767,109 +1649,106 @@ packages: resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.0': - resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0': - resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0': - resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0': - resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': - resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0': - resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0': - resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0': - resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0': - resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0': - resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0': - resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0': - resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0': - resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0': - resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0': - resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0': - resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rolldown/pluginutils@1.0.0-rc.7': - resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -1877,180 +1756,9 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} - cpu: [x64] - os: [win32] - '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rushstack/node-core-library@5.19.1': - resolution: {integrity: sha512-ESpb2Tajlatgbmzzukg6zyAhH+sICqJR2CNXNhXcEbz6UGCQfrKCtkxOpJTftWc8RGouroHG0Nud1SJAszvpmA==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/problem-matcher@0.1.1': - resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/rig-package@0.6.0': - resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} - - '@rushstack/terminal@0.21.0': - resolution: {integrity: sha512-cLaI4HwCNYmknM5ns4G+drqdEB6q3dCPV423+d3TZeBusYSSm09+nR7CnhzJMjJqeRcdMAaLnrA4M/3xDz4R3w==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/ts-command-line@5.2.0': - resolution: {integrity: sha512-lYxCX0nDdkDtCkVpvF0m25ymf66SaMWuppbD6b7MdkIzvGXKBXNIVZlwBH/C0YfkanrupnICWf2n4z3AKSfaHw==} - - '@sinclair/typebox@0.27.10': - resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} - '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -2061,23 +1769,27 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@storybook/addon-docs@10.3.5': - resolution: {integrity: sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA==} + '@storybook/addon-docs@10.4.5': + resolution: {integrity: sha512-9mIV0maIxixfuvdpNhr3QMeU/gbJKeaBcWhPYuf176cqDZAG9EUhZ50TIinxeFRbyEGRJqaLPoiYwIu4GJu3jA==} peerDependencies: - storybook: ^10.3.5 + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.5 + peerDependenciesMeta: + '@types/react': + optional: true - '@storybook/addon-themes@10.3.5': - resolution: {integrity: sha512-Mv+C7GuZ0MhGRx5C+rv8sCEjgYsDTLBvq68101V0s8Vwh3gKd6W9cbS31HoOeLAiIMiPPZ8C1iWudA3Oumdtlw==} + '@storybook/addon-themes@10.4.5': + resolution: {integrity: sha512-UrGgxrgNLLutOOtkBiXOHhF2uJCqN8VG7qtQhhCPNkoSy7ylaIJmvIYoEhjopIeweaFzAzrVjO4jzMYOKKTRvA==} peerDependencies: - storybook: ^10.3.5 + storybook: ^10.4.5 - '@storybook/addon-vitest@10.3.5': - resolution: {integrity: sha512-PQDeeMwoF55kvzlhFqVKOryBJskkVk71AbDh7F0y8PdRRxlGbTvIUkKXktHZWBdESo0dV6BkeVxGQ4ZpiFxirg==} + '@storybook/addon-vitest@10.4.5': + resolution: {integrity: sha512-dyWNyPPXkJg51j7BtCq2d4M2d/81Jv7c+Cw4GbHzKciCfEfDcz82/zEOezanSxrqKvVYWom6Av3oatOzG1pb6w==} peerDependencies: '@vitest/browser': ^3.0.0 || ^4.0.0 '@vitest/browser-playwright': ^4.0.0 '@vitest/runner': ^3.0.0 || ^4.0.0 - storybook: ^10.3.5 + storybook: ^10.4.5 vitest: ^3.0.0 || ^4.0.0 peerDependenciesMeta: '@vitest/browser': @@ -2089,18 +1801,18 @@ packages: vitest: optional: true - '@storybook/builder-vite@10.3.5': - resolution: {integrity: sha512-i4KwCOKbhtlbQIbhm53+Kk7bMnxa0cwTn1pxmtA/x5wm1Qu7FrrBQV0V0DNjkUqzcSKo1CjspASJV/HlY0zYlw==} + '@storybook/builder-vite@10.4.5': + resolution: {integrity: sha512-UFtMIojDT61OWhc2ti0wf6pUNlBRLYixyygXXTolaCxdACum6SOgJim+mEZE4S3VuTaZgjTRNyoc0WSJPqjQ2g==} peerDependencies: - storybook: ^10.3.5 + storybook: ^10.4.5 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@storybook/csf-plugin@10.3.5': - resolution: {integrity: sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw==} + '@storybook/csf-plugin@10.4.5': + resolution: {integrity: sha512-OsSsSLulBmdKTz7MIKLgoWADZB8bjYaAjZZy/THdI50G/TTd6FVSXQMCM7GO7xQZ/EguRY1PmjOVCLbgcnXsDA==} peerDependencies: esbuild: '*' rollup: '*' - storybook: ^10.3.5 + storybook: ^10.4.5 vite: '*' webpack: '*' peerDependenciesMeta: @@ -2116,35 +1828,48 @@ packages: '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - '@storybook/icons@2.0.1': - resolution: {integrity: sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==} + '@storybook/icons@2.0.2': + resolution: {integrity: sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/react-dom-shim@10.3.5': - resolution: {integrity: sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g==} + '@storybook/react-dom-shim@10.4.5': + resolution: {integrity: sha512-fKdikHC7cDgSuaBirPwvgFBmfO//3cln0y3GmDEQchUV2VFDrZ7ZL1/iH7dA21XuiFFhQcDRRkArJmvMAGG5Cg==} peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.5 + storybook: ^10.4.5 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@storybook/react-vite@10.3.5': - resolution: {integrity: sha512-UB5sJHeh26bfd8sNMx2YPGYRYmErIdTRaLOT28m4bykQIa1l9IgVktsYg/geW7KsJU0lXd3oTbnUjLD+enpi3w==} + '@storybook/react-vite@10.4.5': + resolution: {integrity: sha512-hppQmaI7UK+bd0VHP4rtPSwQptL+F0DkGy3ZwH89z2+KqNp8vmX2BFTKKaGRDc+wie8f/0fxWNLwSP2ZYUhG/Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.5 + storybook: ^10.4.5 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@storybook/react@10.3.5': - resolution: {integrity: sha512-tpLTLaVGoA6fLK3ReyGzZUricq7lyPaV2hLPpj5wqdXLV/LpRtAHClUpNoPDYSBjlnSjL81hMZijbkGC3mA+gw==} + '@storybook/react@10.4.5': + resolution: {integrity: sha512-VMEqdjplKJTf8KA5ER9kJp51dOJKlgvb9H0F45RL1pX42+briPmTe8VL0yJR3GyMBpicFbFpjZ7AC22DgZ6vNg==} peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.5 + storybook: ^10.4.5 typescript: '>= 4.9.x' peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true typescript: optional: true @@ -2177,11 +1902,11 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tokens-studio/sd-transforms@1.2.11': - resolution: {integrity: sha512-IVaXTkl15N0YLLhtYQ1igmSJDc28nK0K+HrsACvnXm1V7NxSQn7UIhgUyZdx2Dc1kqDFUsku1NrAnWBKJH55ig==} + '@tokens-studio/sd-transforms@2.0.3': + resolution: {integrity: sha512-PyrmRb7FuJBHzsbuWNk/O06hJbaZ+RL7chmK7PRbEptaSruhANai7Kxja5CiYnTcxOj373uRMDPxoFwCHaVpvA==} engines: {node: '>=18.0.0'} peerDependencies: - style-dictionary: '>=4.3.0 < 6' + style-dictionary: ^5.0.0 '@tokens-studio/tokenscript-interpreter@0.26.0': resolution: {integrity: sha512-dGjvUJnXRspWYp98FZw43l4cN+0ey/cF5sEJjL3coKc5C7DY7MsKgkmOONizmaZqf13GUIzklTEas3gt3jvrOQ==} @@ -2191,16 +1916,9 @@ packages: '@tokens-studio/types@0.5.2': resolution: {integrity: sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==} - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@types/argparse@1.0.38': - resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} - '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -2225,9 +1943,6 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -2237,25 +1952,19 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/mdx@2.0.13': - resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} - '@types/node@22.19.9': - resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==} - - '@types/node@25.2.1': - resolution: {integrity: sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==} - - '@types/node@25.7.0': - resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} + '@types/node@25.9.3': + resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} '@types/resolve@1.20.6': resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} @@ -2263,8 +1972,8 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@vitejs/plugin-react@6.0.1': - resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -2276,73 +1985,34 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/browser-playwright@4.1.6': - resolution: {integrity: sha512-4csoeyl/qwHyxU2zNL0++WaoDr8YJDXOQPwWPNJoTZ+QzcdO3INYKgF5Zfz730Io7zbkuv914aZmfQ+QE+1Hvw==} + '@vitest/browser-playwright@4.1.9': + resolution: {integrity: sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw==} peerDependencies: playwright: '*' - vitest: 4.1.6 + vitest: 4.1.9 - '@vitest/browser@1.6.1': - resolution: {integrity: sha512-9ZYW6KQ30hJ+rIfJoGH4wAub/KAb4YrFzX0kVLASvTm7nJWVC5EAv5SlzlXVl3h3DaUq5aqHlZl77nmOPnALUQ==} + '@vitest/browser@4.1.9': + resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==} peerDependencies: - playwright: '*' - safaridriver: '*' - vitest: 1.6.1 - webdriverio: '*' - peerDependenciesMeta: - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true + vitest: 4.1.9 - '@vitest/browser@4.1.3': - resolution: {integrity: sha512-CS9KjO2vijuBlbwz0JIgC0YuoI1BuqWI5ziD3Nll6jkpNYtWdjPMVgGynQ9vZovjsECeUqEeNjWrypP414d0CQ==} + '@vitest/coverage-v8@4.1.9': + resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==} peerDependencies: - vitest: 4.1.3 - - '@vitest/browser@4.1.6': - resolution: {integrity: sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==} - peerDependencies: - vitest: 4.1.6 - - '@vitest/coverage-v8@1.6.1': - resolution: {integrity: sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==} - peerDependencies: - vitest: 1.6.1 - - '@vitest/coverage-v8@4.1.3': - resolution: {integrity: sha512-/MBdrkA8t6hbdCWFKs09dPik774xvs4Z6L4bycdCxYNLHM8oZuRyosumQMG19LUlBsB6GeVpL1q4kFFazvyKGA==} - peerDependencies: - '@vitest/browser': 4.1.3 - vitest: 4.1.3 + '@vitest/browser': 4.1.9 + vitest: 4.1.9 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@1.6.1': - resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.6': - resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} - '@vitest/mocker@4.1.3': - resolution: {integrity: sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/mocker@4.1.6': - resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2355,52 +2025,31 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.1.3': - resolution: {integrity: sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==} + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} - '@vitest/pretty-format@4.1.6': - resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} - '@vitest/runner@1.6.1': - resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} - - '@vitest/runner@4.1.6': - resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} - - '@vitest/snapshot@1.6.1': - resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} - - '@vitest/snapshot@4.1.6': - resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} - - '@vitest/spy@1.6.1': - resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.3': - resolution: {integrity: sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==} + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} - '@vitest/spy@4.1.6': - resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} - - '@vitest/ui@1.6.1': - resolution: {integrity: sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg==} + '@vitest/ui@4.1.9': + resolution: {integrity: sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ==} peerDependencies: - vitest: 1.6.1 - - '@vitest/utils@1.6.1': - resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + vitest: 4.1.9 '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.1.3': - resolution: {integrity: sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==} - - '@vitest/utils@4.1.6': - resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -2411,36 +2060,13 @@ packages: '@volar/typescript@2.4.28': resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - '@vue/compiler-core@3.5.27': - resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==} - - '@vue/compiler-dom@3.5.27': - resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==} - - '@vue/compiler-vue2@2.7.16': - resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - - '@vue/language-core@2.2.0': - resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@vue/shared@3.5.27': - resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} - '@webcontainer/env@1.1.1': resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} - '@xmldom/xmldom@0.8.11': - resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} - '@yarnpkg/lockfile@1.1.0': - resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - '@zip.js/zip.js@2.8.26': resolution: {integrity: sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==} engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} @@ -2458,17 +2084,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} hasBin: true @@ -2476,37 +2093,11 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} - peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - - ajv@8.13.0: - resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} - - alien-signals@0.4.14: - resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -2536,9 +2127,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2552,8 +2140,8 @@ packages: arkregex@0.0.5: resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} - arktype@2.1.29: - resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==} + arktype@2.2.0: + resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} @@ -2593,9 +2181,6 @@ packages: assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2607,8 +2192,8 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} - ast-v8-to-istanbul@1.0.0: - resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + ast-v8-to-istanbul@1.0.4: + resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} @@ -2629,21 +2214,18 @@ packages: engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.1: - resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + axe-core@4.12.1: + resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} engines: {node: '>=4'} - axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - - axios@1.16.1: - resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} + axios@1.17.0: + resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2662,15 +2244,11 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.29: - resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + baseline-browser-mapping@2.10.36: + resolution: {integrity: sha512-lVq/Df7LXlO79MVaaUHztSwWiG9oXoWHlgvNS51v8Dpd4+G4/VIy6qYePTw31nAVls33nUtnfezYeLkYAak9dg==} engines: {node: '>=6.0.0'} hasBin: true - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} - hasBin: true - bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -2678,11 +2256,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - binary-install@1.1.2: - resolution: {integrity: sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==} - engines: {node: '>=10'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} @@ -2696,14 +2269,15 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + boolbase@2.0.0: + resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} + engines: {node: '>=20.19.0'} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} @@ -2713,19 +2287,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2743,10 +2309,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - cacheable@2.3.5: resolution: {integrity: sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==} @@ -2754,8 +2316,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -2766,20 +2328,13 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} - caniuse-lite@1.0.30001792: - resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} - - canvas@3.2.1: - resolution: {integrity: sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==} + canvas@3.2.3: + resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} engines: {node: ^18.12.0 || >= 20.9.0} - chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} - chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -2803,9 +2358,6 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -2814,21 +2366,13 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - clean-css@4.2.4: resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==} engines: {node: '>= 4.0'} @@ -2837,6 +2381,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clone-buffer@1.0.0: resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} engines: {node: '>= 0.10'} @@ -2891,9 +2439,6 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - colorjs.io@0.4.5: - resolution: {integrity: sha512-yCtUNCmge7llyfd/Wou19PMAcf5yC3XXhgFoAh6zsO2pGswhUPBaaUh8jzgHnXtXuZyFKzXZNAnyF5i+apICow==} - colorjs.io@0.5.2: resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} @@ -2935,28 +2480,32 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} - engines: {node: '>=18'} + concurrently@10.0.3: + resolution: {integrity: sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==} + engines: {node: '>=22'} hasBin: true confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2968,14 +2517,14 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - core-js-pure@3.48.0: - resolution: {integrity: sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==} + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cosmiconfig@9.0.1: - resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -3001,8 +2550,9 @@ packages: css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-select@7.0.0: + resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==} + engines: {node: '>=20.19.0'} css-selector-parser@1.4.1: resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==} @@ -3015,10 +2565,6 @@ packages: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -3027,6 +2573,10 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + css-what@8.0.0: + resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==} + engines: {node: '>=20.19.0'} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -3046,20 +2596,12 @@ packages: cssom@0.5.0: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - cssstyle@5.3.7: - resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} - engines: {node: '>=20'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - data-urls@6.0.1: - resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} - engines: {node: '>=20'} - data-urls@7.0.0: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -3076,11 +2618,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - - de-indent@1.0.2: - resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -3114,10 +2653,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} - engines: {node: '>=6'} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3165,9 +2700,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-europe-js@0.1.2: - resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -3175,14 +2707,6 @@ packages: dettle@1.0.5: resolution: {integrity: sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} - engines: {node: '>=0.3.1'} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -3203,28 +2727,34 @@ packages: dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} + domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0: - resolution: {tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0} + resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0} version: 0.11.7 peerDependencies: react: '>=0.14.0' @@ -3237,19 +2767,19 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - editorconfig@1.0.4: - resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + editorconfig@1.0.7: + resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} engines: {node: '>=14'} hasBin: true ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.372: + resolution: {integrity: sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==} - electron-to-chromium@1.5.355: - resolution: {integrity: sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3257,8 +2787,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - empathic@2.0.0: - resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} enabled@2.0.0: @@ -3277,18 +2807,6 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} @@ -3300,8 +2818,8 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -3312,15 +2830,15 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.2.2: - resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + es-iterator-helpers@1.3.3: + resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==} engines: {node: '>= 0.4'} es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -3335,23 +2853,13 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true - esbuild@0.27.4: - resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -3370,11 +2878,11 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + eslint-module-utils@2.13.0: + resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -3410,11 +2918,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} engines: {node: '>=18'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -3434,8 +2942,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3483,14 +2991,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.0.8: - resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} engines: {node: '>=18.0.0'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -3499,8 +3003,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - expr-eval-fork@2.0.2: - resolution: {integrity: sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==} + expr-eval-fork@3.0.3: + resolution: {integrity: sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg==} + engines: {node: '>=16.9.0'} express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} @@ -3526,6 +3031,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -3551,8 +3059,8 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} file-entry-cache@11.1.3: resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} @@ -3573,9 +3081,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-yarn-workspace-root@2.0.0: - resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -3583,24 +3088,12 @@ packages: flat-cache@6.1.22: resolution: {integrity: sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} @@ -3636,18 +3129,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-extra@11.3.3: - resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} - engines: {node: '>=14.14'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -3664,8 +3145,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -3690,9 +3171,6 @@ packages: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -3701,10 +3179,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -3738,10 +3212,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.1: - resolution: {integrity: sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==} - engines: {node: 20 || >=22} - glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -3815,14 +3285,10 @@ packages: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -3857,22 +3323,10 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - hyperdyperid@1.2.0: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} @@ -3889,7 +3343,7 @@ packages: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3905,24 +3359,17 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - immutable@3.7.6: - resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==} - engines: {node: '>=0.8.0'} + immutable@3.8.3: + resolution: {integrity: sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==} + engines: {node: '>=0.10.0'} - immutable@5.1.4: - resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} - - immutable@5.1.5: - resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + immutable@5.1.6: + resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -3986,8 +3433,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -3998,16 +3445,15 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -4086,17 +3532,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-standalone-pwa@0.1.1: - resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -4121,12 +3560,8 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} isarray@1.0.0: @@ -4146,10 +3581,6 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - istanbul-reports@3.2.0: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} @@ -4161,9 +3592,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - joi@18.2.1: resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==} engines: {node: '>= 20'} @@ -4173,9 +3601,8 @@ packages: engines: {node: '>=14'} hasBin: true - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -4183,22 +3610,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true - jsdom@27.4.0: - resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - jsdom@29.1.1: resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -4231,10 +3646,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stable-stringify@1.3.0: - resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} - engines: {node: '>= 0.4'} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -4244,12 +3655,6 @@ packages: engines: {node: '>=6'} hasBin: true - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - jsonify@0.0.1: - resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -4264,9 +3669,6 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - klaw-sync@6.0.0: - resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} - known-css-properties@0.37.0: resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} @@ -4372,12 +3774,8 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} engines: {node: '>=14'} locate-path@6.0.0: @@ -4399,9 +3797,6 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -4413,30 +3808,19 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.5: - resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} - engines: {node: 20 || >=22} - - lru-cache@11.3.6: - resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -4444,11 +3828,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -4457,8 +3838,8 @@ packages: map-stream@0.0.7: resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==} - marked@17.0.6: - resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} engines: {node: '>= 20'} hasBin: true @@ -4475,9 +3856,6 @@ packages: mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -4485,8 +3863,8 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memfs@4.56.10: - resolution: {integrity: sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==} + memfs@4.57.7: + resolution: {integrity: sha512-YZPphUQZSRGk6ddPlsNuMbztrLwsbUATFNZcqKscSbSJZ4g0+Y3vSZLJ/rfnGZaB1FFhC7SrywZXev6i8lnHgg==} peerDependencies: tslib: '2' @@ -4502,9 +3880,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -4529,10 +3904,6 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -4541,66 +3912,34 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.1.2: - resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} - engines: {node: 20 || >=22} - minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} - engines: {node: '>=16 || 14 >=14.17'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} @@ -4612,15 +3951,12 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -4641,13 +3977,17 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - node-abi@3.87.0: - resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} engines: {node: '>=10'} node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -4657,11 +3997,9 @@ packages: encoding: optional: true - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - node-releases@2.0.44: - resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} nodemon@3.1.14: resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} @@ -4685,13 +4023,13 @@ packages: engines: {node: '>= 4'} hasBin: true - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nth-check@3.0.1: + resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==} + engines: {node: '>=20.19.0'} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4728,8 +4066,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -4745,21 +4084,12 @@ packages: one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} - open@7.4.2: - resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} - engines: {node: '>=8'} - - opentype.js@1.3.4: - resolution: {integrity: sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==} - engines: {node: '>= 8.0.0'} + opentype.js@2.0.0: + resolution: {integrity: sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==} hasBin: true optionator@0.9.4: @@ -4770,14 +4100,17 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.20.0: + resolution: {integrity: sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-limit@7.3.0: resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} engines: {node: '>=20'} @@ -4801,9 +4134,6 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} - parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -4811,11 +4141,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - patch-package@8.0.1: - resolution: {integrity: sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==} - engines: {node: '>=14', npm: '>5'} - hasBin: true - path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -4835,10 +4160,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4846,16 +4167,12 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.1: - resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} - engines: {node: 20 || >=22} - path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -4867,15 +4184,9 @@ packages: path@0.12.7: resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} @@ -4886,14 +4197,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -4910,26 +4217,16 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - playwright-core@1.58.0: - resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==} + playwright-core@1.61.0: + resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} engines: {node: '>=18'} hasBin: true - playwright-core@1.60.0: - resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.58.0: - resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.60.0: - resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + playwright@1.61.0: + resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} engines: {node: '>=18'} hasBin: true @@ -4956,30 +4253,30 @@ packages: resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 postcss-modules-local-by-default@4.2.0: resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 postcss-modules-scope@3.2.1: resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 postcss-modules-values@4.0.0: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: ^8.5.10 postcss-modules@6.0.1: resolution: {integrity: sha512-zyo2sAkVvuZFFy0gc2+4O+xar5dYlaVy/ebO24KT0ftk/iJevSNyPyQellsBLlnccwh7f6V6Y4GvuKRYToNgpQ==} peerDependencies: - postcss: ^8.0.0 + postcss: ^8.5.10 postcss-resolve-nested-selector@0.1.6: resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} @@ -4988,16 +4285,16 @@ packages: resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.4.31 + postcss: ^8.5.10 postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} engines: {node: '>=12.0'} peerDependencies: - postcss: ^8.4.29 + postcss: ^8.5.10 - postcss-selector-parser@7.1.1: - resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + postcss-selector-parser@7.1.4: + resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} engines: {node: '>=4'} postcss-value-parser@3.3.1: @@ -5006,25 +4303,22 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@6.0.23: - resolution: {integrity: sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==} - engines: {node: '>=4.0.0'} - - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} engines: {node: '>=14'} hasBin: true @@ -5032,10 +4326,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-time@1.1.0: resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} engines: {node: '>=4'} @@ -5076,8 +4366,8 @@ packages: pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -5090,8 +4380,8 @@ packages: resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -5125,22 +4415,17 @@ packages: peerDependencies: typescript: '>= 4.3.x' - react-docgen@8.0.2: - resolution: {integrity: sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==} + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} engines: {node: ^20.9.0 || >=22} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: - react: ^19.2.3 + react: ^19.2.7 - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} - peerDependencies: - react: ^19.2.4 - - react-error-boundary@6.1.1: - resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} + react-error-boundary@6.1.2: + resolution: {integrity: sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -5150,9 +4435,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} @@ -5162,12 +4444,8 @@ packages: react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} - engines: {node: '>=0.10.0'} - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} read-pkg@3.0.0: @@ -5185,9 +4463,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} readline-sync@1.4.10: resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==} @@ -5228,39 +4506,30 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} hasBin: true reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rimraf@6.1.3: resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} hasBin: true - rolldown@1.0.0: - resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -5278,8 +4547,8 @@ packages: rxjs@8.0.0-alpha.14: resolution: {integrity: sha512-oRCzFwbAMbo0+dVUeGUCCEf339mW7CESFvVDG3RTncD6yMtV2XoubFMpKEhBd2a9d04EpHl8QSz84/V/agG6bw==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -5303,130 +4572,135 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-embedded-all-unknown@1.99.0: - resolution: {integrity: sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw==} + sass-embedded-all-unknown@1.100.0: + resolution: {integrity: sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q==} cpu: ['!arm', '!arm64', '!riscv64', '!x64'] - sass-embedded-android-arm64@1.99.0: - resolution: {integrity: sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg==} + sass-embedded-android-arm64@1.100.0: + resolution: {integrity: sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] - sass-embedded-android-arm@1.99.0: - resolution: {integrity: sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ==} + sass-embedded-android-arm@1.100.0: + resolution: {integrity: sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] - sass-embedded-android-riscv64@1.99.0: - resolution: {integrity: sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw==} + sass-embedded-android-riscv64@1.100.0: + resolution: {integrity: sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] - sass-embedded-android-x64@1.99.0: - resolution: {integrity: sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ==} + sass-embedded-android-x64@1.100.0: + resolution: {integrity: sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] - sass-embedded-darwin-arm64@1.99.0: - resolution: {integrity: sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg==} + sass-embedded-darwin-arm64@1.100.0: + resolution: {integrity: sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] - sass-embedded-darwin-x64@1.99.0: - resolution: {integrity: sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A==} + sass-embedded-darwin-x64@1.100.0: + resolution: {integrity: sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] - sass-embedded-linux-arm64@1.99.0: - resolution: {integrity: sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew==} + sass-embedded-linux-arm64@1.100.0: + resolution: {integrity: sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] libc: glibc - sass-embedded-linux-arm@1.99.0: - resolution: {integrity: sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==} + sass-embedded-linux-arm@1.100.0: + resolution: {integrity: sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] libc: glibc - sass-embedded-linux-musl-arm64@1.99.0: - resolution: {integrity: sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==} + sass-embedded-linux-musl-arm64@1.100.0: + resolution: {integrity: sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] libc: musl - sass-embedded-linux-musl-arm@1.99.0: - resolution: {integrity: sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==} + sass-embedded-linux-musl-arm@1.100.0: + resolution: {integrity: sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] libc: musl - sass-embedded-linux-musl-riscv64@1.99.0: - resolution: {integrity: sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==} + sass-embedded-linux-musl-riscv64@1.100.0: + resolution: {integrity: sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] libc: musl - sass-embedded-linux-musl-x64@1.99.0: - resolution: {integrity: sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==} + sass-embedded-linux-musl-x64@1.100.0: + resolution: {integrity: sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] libc: musl - sass-embedded-linux-riscv64@1.99.0: - resolution: {integrity: sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==} + sass-embedded-linux-riscv64@1.100.0: + resolution: {integrity: sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] libc: glibc - sass-embedded-linux-x64@1.99.0: - resolution: {integrity: sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==} + sass-embedded-linux-x64@1.100.0: + resolution: {integrity: sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] libc: glibc - sass-embedded-unknown-all@1.99.0: - resolution: {integrity: sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==} + sass-embedded-unknown-all@1.100.0: + resolution: {integrity: sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A==} os: ['!android', '!darwin', '!linux', '!win32'] - sass-embedded-win32-arm64@1.99.0: - resolution: {integrity: sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw==} + sass-embedded-win32-arm64@1.100.0: + resolution: {integrity: sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [win32] - sass-embedded-win32-x64@1.99.0: - resolution: {integrity: sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg==} + sass-embedded-win32-x64@1.100.0: + resolution: {integrity: sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] - sass-embedded@1.99.0: - resolution: {integrity: sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==} + sass-embedded@1.100.0: + resolution: {integrity: sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw==} engines: {node: '>=16.0.0'} hasBin: true - sass@1.99.0: - resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} - engines: {node: '>=14.0.0'} + sass@1.100.0: + resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} + engines: {node: '>=20.19.0'} hasBin: true - sax@1.4.4: - resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + sass@1.101.0: + resolution: {integrity: sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==} + engines: {node: '>=20.19.0'} + hasBin: true + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} saxes@6.0.0: @@ -5444,13 +4718,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} engines: {node: '>=10'} hasBin: true @@ -5496,12 +4765,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -5512,8 +4781,8 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} siginfo@2.0.0: @@ -5533,18 +4802,10 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - sirv@2.0.4: - resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} - engines: {node: '>= 10'} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} - slash@2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} - slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -5573,11 +4834,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} stable@0.1.8: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} @@ -5593,9 +4851,6 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} @@ -5603,22 +4858,24 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - storybook@10.3.5: - resolution: {integrity: sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw==} + storybook@10.4.5: + resolution: {integrity: sha512-QZuv1gS9Tf9RMCjDw5JOfv1XSB5IhU0uhSKQNS7l/N9zDpmSydirCspkCNT9e0zkFfPkZ9vmQUTzHY/BA07saA==} hasBin: true peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 prettier: ^2 || ^3 + vite-plus: ^0.1.15 peerDependenciesMeta: + '@types/react': + optional: true prettier: optional: true + vite-plus: + optional: true stream@0.0.3: resolution: {integrity: sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==} - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - string-hash@1.1.3: resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} @@ -5630,13 +4887,14 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} - string.prototype.codepointat@0.2.1: - resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} - string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -5652,12 +4910,12 @@ packages: string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: @@ -5674,18 +4932,14 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -5702,14 +4956,11 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@2.1.1: - resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} - stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} - style-dictionary@5.0.0-rc.1: - resolution: {integrity: sha512-A1ZG8Lmv3KwMcgmEgrdLd3v8zrJaihmvyphcFphxCFrr/7pJHEaZbQ7JPKx1dRVSRqqAX5GGVGXndOnTKw0vBA==} + style-dictionary@5.4.4: + resolution: {integrity: sha512-Sd7dkEOLn33EpvlMyS8ykUu0us2EIFUqsysf4DSwZQ46nqQkZ2Q9o5mozu8cSizj3t7E220HaPk7EV1O7LjvDQ==} engines: {node: '>=22.0.0'} hasBin: true @@ -5717,7 +4968,7 @@ packages: resolution: {integrity: sha512-x5DVehzJudcwF0od3sGpgkln2PLLranFE7twwbp7dqDINCyZvwzFkMc6TLhNOvazRiVBJYATQLouJY0xPGB8WA==} engines: {node: '>=20'} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.5.10 stylelint: ^17.0.0 peerDependenciesMeta: postcss: @@ -5733,7 +4984,7 @@ packages: resolution: {integrity: sha512-uLJS6xgOCBw5EMsDW7Ukji8l28qRoMnkRch15s0qwZpskXvWt9oPzMmcYM307m9GN4MxuWLsQh4I6hU9yI53cQ==} engines: {node: '>=20'} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.5.10 stylelint: ^17.0.0 peerDependenciesMeta: postcss: @@ -5745,8 +4996,8 @@ packages: peerDependencies: stylelint: ^17.0.0 - stylelint-scss@7.1.1: - resolution: {integrity: sha512-pLPXJZ7RtAFNLXe8gqarf3B56ScVTd1vPiL9IFgcJkIZsYPzAgLJPh2h9NHrp3BFeKrtAkIgwQ08QSmhvlv1gA==} + stylelint-scss@7.2.0: + resolution: {integrity: sha512-6E79Bachv0Iz0gqRUZgdqdXCsiq26DWBWIBNHYtjTmAp3wJu6cp/I37VfW7BPntmh2puF3bY09XWl4HZGrLhzw==} engines: {node: '>=20.19.0'} peerDependencies: stylelint: ^16.8.2 || ^17.0.0 @@ -5757,8 +5008,8 @@ packages: peerDependencies: stylelint: '>=11 < 17' - stylelint@17.11.0: - resolution: {integrity: sha512-/3czzmbF9XdGWvReDF3Ex4R23Ajolo7j8RB2bFNEqk6Ht356nlpVV+G5bG2Qt8AW1ofJzXztBRDnAtd7cgowWA==} + stylelint@17.13.0: + resolution: {integrity: sha512-G1WYzMerp7ihOaIe9VJCHLt12MoAD2QLf1AFerYP37+BCRBUK5UCpq8e/mN+zCIaJPKQcaxhE4WlPmqdiOx/gw==} engines: {node: '>=20.19.0'} hasBin: true @@ -5794,16 +5045,11 @@ packages: svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + svgo@2.8.2: + resolution: {integrity: sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==} engines: {node: '>=10.13.0'} hasBin: true - svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b: - resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b} - version: 4.0.0 - engines: {node: '>=16.0.0'} - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -5826,30 +5072,18 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - tdigest@0.1.2: resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - thingies@2.5.0: - resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} + thingies@2.6.0: + resolution: {integrity: sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==} engines: {node: '>=10.18'} peerDependencies: tslib: ^2 - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -5862,22 +5096,14 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -5886,25 +5112,17 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.22: - resolution: {integrity: sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==} + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} - tldts@7.0.22: - resolution: {integrity: sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw==} + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} hasBin: true - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} - engines: {node: '>=14.14'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -5921,10 +5139,6 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} - engines: {node: '>=16'} - tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -5950,8 +5164,8 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} engines: {node: '>=6.10'} tsconfig-paths@3.15.0: @@ -5971,13 +5185,9 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} - - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -5991,33 +5201,21 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} - engines: {node: '>=14.17'} - hasBin: true - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true - ua-is-frozen@0.1.2: - resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} - ua-parser-js@1.0.41: resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} hasBin: true - ua-parser-js@2.0.9: - resolution: {integrity: sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==} - hasBin: true - - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} @@ -6026,31 +5224,51 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - undici-types@7.21.0: - resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} - - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + undici@7.27.2: + resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} engines: {node: '>=20.18.1'} unicorn-magic@0.4.0: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} engines: {node: '>=20'} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-dts@1.0.2: + resolution: {integrity: sha512-VbNiMD0LMl/t6nJueGtrCp79N7ZO1nquxj/FUybJDnKwZGsnW2wjdwBSzA3QEHujoxmxZIptsG43hL7LzXE96w==} + peerDependencies: + '@microsoft/api-extractor': '>=7' + '@rspack/core': ^1 + '@vue/language-core': ~3.1.5 + esbuild: '*' + rolldown: '*' + rollup: '>=3' + typescript: '>=4' + vite: '>=3' + webpack: ^4 || ^5 + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@rspack/core': + optional: true + '@vue/language-core': + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -6096,53 +5314,22 @@ packages: resolution: {integrity: sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==} engines: {node: '>= 0.10'} - vite-node@1.6.1: - resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-plugin-dts@4.5.4: - resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + vite-plugin-dts@5.0.2: + resolution: {integrity: sha512-lNeHS+dwGju6eRmNvZQt8Shwv9j3m98hbHse/lIbLq9q3yE2DcIOBBYQEVUF6tS0kOmv+VA9Z5FqmzFnGe4U8g==} peerDependencies: - typescript: '*' - vite: '*' + '@microsoft/api-extractor': '>=7' + rollup: '>=3' + vite: '>=3' peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + rollup: + optional: true vite: optional: true - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vite@8.0.12: - resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==} + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6157,7 +5344,7 @@ packages: sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 - yaml: ^2.4.2 + yaml: ^2.8.3 peerDependenciesMeta: '@types/node': optional: true @@ -6184,45 +5371,20 @@ packages: yaml: optional: true - vitest@1.6.1: - resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.1 - '@vitest/ui': 1.6.1 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - vitest@4.1.6: - resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.6 - '@vitest/browser-preview': 4.1.6 - '@vitest/browser-webdriverio': 4.1.6 - '@vitest/coverage-istanbul': 4.1.6 - '@vitest/coverage-v8': 4.1.6 - '@vitest/ui': 4.1.6 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6262,10 +5424,6 @@ packages: engines: {node: '>=20.0.0'} hasBin: true - wasm-pack@0.13.1: - resolution: {integrity: sha512-P9exD4YkjpDbw68xUhF3MDm/CC/3eTmmthyG5bHJ56kalxOTewOunxTke4SyF8MTXV6jUtNjXggPgrGmMtczGg==} - hasBin: true - watcher@2.3.1: resolution: {integrity: sha512-d3yl+ey35h05r5EFP0TafE2jsmQUJ9cc2aernRVyAkZiu0J3+3TbNugNcqdUJDoWOfL2p+bNsN427stsBC/HnA==} @@ -6279,18 +5437,10 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} - whatwg-url@15.1.0: - resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} - engines: {node: '>=20'} - whatwg-url@16.0.1: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -6310,8 +5460,8 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} engines: {node: '>= 0.4'} which@1.3.1: @@ -6351,6 +5501,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -6358,8 +5512,8 @@ packages: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6395,24 +5549,24 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yauzl@3.2.0: - resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yauzl@3.4.0: + resolution: {integrity: sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==} engines: {node: '>=12'} yocto-queue@0.1.0: @@ -6429,19 +5583,12 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: - '@acemir/cssom@0.9.31': {} - - '@adobe/css-tools@4.4.4': {} - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@adobe/css-tools@4.5.0': {} '@ark/schema@0.56.0': dependencies: @@ -6449,30 +5596,14 @@ snapshots: '@ark/util@0.56.0': {} - '@asamuzakjp/css-color@4.1.2': - dependencies: - '@csstools/css-calc': 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.2.5 - '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@asamuzakjp/dom-selector@6.7.8': - dependencies: - '@asamuzakjp/nwsapi': 2.3.9 - bidi-js: 1.0.3 - css-tree: 3.1.0 - is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.5 - '@asamuzakjp/dom-selector@7.1.1': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -6485,25 +5616,25 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.0': + '@babel/core@7.29.7(supports-color@5.5.0)': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@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/core@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.4.3(supports-color@5.5.0) @@ -6513,137 +5644,135 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': + '@babel/generator@7.29.7': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.27.3': + '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 - '@babel/helper-compilation-targets@7.28.6': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} - '@babel/helpers@7.28.6': + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 - '@babel/parser@7.29.0': + '@babel/parser@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/types': 7.29.0 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/preset-react@7.28.5(@babel/core@7.29.0)': + '@babel/preset-react@7.29.7(@babel/core@7.29.7(supports-color@5.5.0))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7(supports-color@5.5.0)) transitivePeerDependencies: - supports-color - '@babel/runtime-corejs3@7.29.0': + '@babel/runtime-corejs3@7.29.7': dependencies: - core-js-pure: 3.48.0 + core-js-pure: 3.49.0 - '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.7': {} - '@babel/template@7.28.6': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@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.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/types@7.29.0': + '@babel/types@7.29.7': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@bcoe/v8-coverage@0.2.3': {} + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@bcoe/v8-coverage@1.0.2': {} @@ -6653,18 +5782,17 @@ snapshots: dependencies: css-tree: 3.2.1 - '@bufbuild/protobuf@2.11.0': {} + '@bufbuild/protobuf@2.12.0': {} - '@bundled-es-modules/deepmerge@4.3.1': + '@bundled-es-modules/deepmerge@4.3.2': dependencies: deepmerge: 4.3.1 - '@bundled-es-modules/glob@10.4.2': + '@bundled-es-modules/glob@13.0.6': dependencies: buffer: 6.0.3 events: 3.3.0 - glob: 10.5.0 - patch-package: 8.0.1 + glob: 13.0.6 path: 0.12.7 stream: 0.0.3 string_decoder: 1.3.0 @@ -6675,7 +5803,7 @@ snapshots: assert: 2.1.0 buffer: 6.0.3 events: 3.3.0 - memfs: 4.56.10(tslib@2.8.1) + memfs: 4.57.7(tslib@2.8.1) path: 0.12.7 stream: 0.0.3 util: 0.12.5 @@ -6686,7 +5814,7 @@ snapshots: dependencies: postcss-calc-ast-parser: 0.1.4 - '@cacheable/memory@2.0.8': + '@cacheable/memory@2.0.9': dependencies: '@cacheable/utils': 2.4.1 '@keyv/bigmap': 1.3.1(keyv@5.6.0) @@ -6700,28 +5828,14 @@ snapshots: '@colors/colors@1.6.0': {} - '@csstools/color-helpers@6.0.1': {} - '@csstools/color-helpers@6.0.2': {} - '@csstools/css-calc@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/color-helpers': 6.0.1 - '@csstools/css-calc': 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -6732,9 +5846,7 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.26': {} - - '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 @@ -6745,13 +5857,13 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/selector-resolve-nested@4.0.0(postcss-selector-parser@7.1.1)': + '@csstools/selector-resolve-nested@4.0.0(postcss-selector-parser@7.1.4)': dependencies: - postcss-selector-parser: 7.1.1 + postcss-selector-parser: 7.1.4 - '@csstools/selector-specificity@6.0.0(postcss-selector-parser@7.1.1)': + '@csstools/selector-specificity@6.0.0(postcss-selector-parser@7.1.4)': dependencies: - postcss-selector-parser: 7.1.1 + postcss-selector-parser: 7.1.4 '@dabh/diagnostics@2.0.8': dependencies: @@ -6765,322 +5877,186 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.21.5': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/aix-ppc64@0.27.3': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/aix-ppc64@0.27.4': + '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/aix-ppc64@0.28.0': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-arm64@0.27.3': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-arm64@0.27.4': + '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/android-arm64@0.28.0': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.27.3': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.4': + '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/android-arm@0.28.0': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/android-x64@0.27.3': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/android-x64@0.27.4': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/android-x64@0.28.0': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.27.3': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.4': + '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/darwin-arm64@0.28.0': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/darwin-x64@0.27.3': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.4': + '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/darwin-x64@0.28.0': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.27.3': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.4': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.28.0': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.27.3': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.4': + '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/freebsd-x64@0.28.0': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.27.3': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.4': + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/linux-arm64@0.28.0': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/linux-arm@0.27.3': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.4': + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.28.0': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/linux-ia32@0.27.3': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.4': + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/linux-ia32@0.28.0': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/linux-loong64@0.27.3': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.4': + '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/linux-loong64@0.28.0': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/linux-mips64el@0.27.3': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.4': + '@esbuild/win32-x64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.28.0': + '@esbuild/win32-x64@0.28.1': optional: true - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.4': - optional: true - - '@esbuild/linux-ppc64@0.28.0': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.4': - optional: true - - '@esbuild/linux-riscv64@0.28.0': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.4': - optional: true - - '@esbuild/linux-s390x@0.28.0': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.4': - optional: true - - '@esbuild/linux-x64@0.28.0': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.4': - optional: true - - '@esbuild/netbsd-arm64@0.28.0': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.4': - optional: true - - '@esbuild/netbsd-x64@0.28.0': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.4': - optional: true - - '@esbuild/openbsd-arm64@0.28.0': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.4': - optional: true - - '@esbuild/openbsd-x64@0.28.0': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.4': - optional: true - - '@esbuild/openharmony-arm64@0.28.0': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.4': - optional: true - - '@esbuild/sunos-x64@0.28.0': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.4': - optional: true - - '@esbuild/win32-arm64@0.28.0': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.4': - optional: true - - '@esbuild/win32-ia32@0.28.0': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - - '@esbuild/win32-x64@0.27.4': - optional: true - - '@esbuild/win32-x64@0.28.0': - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': dependencies: - eslint: 9.39.2 + eslint: 9.39.4 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -7109,13 +6085,13 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.39.2': {} + '@eslint/js@9.39.4': {} '@eslint/object-schema@2.1.7': {} @@ -7124,9 +6100,7 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.11.0': {} - - '@exodus/bytes@1.15.0': {} + '@exodus/bytes@1.15.1': {} '@hapi/address@5.1.1': dependencies: @@ -7138,7 +6112,7 @@ snapshots: '@hapi/pinpoint@2.0.1': {} - '@hapi/tlds@1.1.4': {} + '@hapi/tlds@1.1.7': {} '@hapi/topo@6.0.2': dependencies: @@ -7160,32 +6134,20 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.1': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@istanbuljs/schema@0.1.3': {} - - '@jest/schemas@29.6.3': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@sinclair/typebox': 0.27.10 - - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - glob: 13.0.1 + glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@6.0.3) - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) optionalDependencies: typescript: 6.0.3 @@ -7212,7 +6174,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/base64@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/base64@17.67.0(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -7220,7 +6182,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/buffers@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/buffers@17.67.0(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -7228,64 +6190,64 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/codegen@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/codegen@17.67.0(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-core@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-fsa@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-node-builtins@4.57.7(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-node-to-fsa@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-node-utils@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-node@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.7(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-print@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': + '@jsonjoy.com/fs-snapshot@4.57.7(tslib@2.8.1)': dependencies: - '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/json-pack': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/json-pack': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': @@ -7296,19 +6258,19 @@ snapshots: '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/json-pack@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/json-pack@17.67.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/base64': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/codegen': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/json-pointer': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/base64': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -7318,9 +6280,9 @@ snapshots: '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/json-pointer@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/json-pointer@17.67.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': @@ -7329,10 +6291,10 @@ snapshots: '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/util@17.65.0(tslib@2.8.1)': + '@jsonjoy.com/util@17.67.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/codegen': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) tslib: 2.8.1 '@keyv/bigmap@1.3.1(keyv@5.6.0)': @@ -7343,55 +6305,26 @@ snapshots: '@keyv/serialize@1.1.1': {} - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)': + '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@types/mdx': 2.0.13 - '@types/react': 19.2.14 - react: 19.2.4 + '@types/mdx': 2.0.14 + '@types/react': 19.2.17 + react: 19.2.7 - '@microsoft/api-extractor-model@7.32.2(@types/node@25.7.0)': - dependencies: - '@microsoft/tsdoc': 0.16.0 - '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) - transitivePeerDependencies: - - '@types/node' - - '@microsoft/api-extractor@7.56.2(@types/node@25.7.0)': - dependencies: - '@microsoft/api-extractor-model': 7.32.2(@types/node@25.7.0) - '@microsoft/tsdoc': 0.16.0 - '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) - '@rushstack/rig-package': 0.6.0 - '@rushstack/terminal': 0.21.0(@types/node@25.7.0) - '@rushstack/ts-command-line': 5.2.0(@types/node@25.7.0) - diff: 8.0.3 - lodash: 4.17.23 - minimatch: 10.1.2 - resolve: 1.22.11 - semver: 7.5.4 - source-map: 0.6.1 - typescript: 5.8.2 - transitivePeerDependencies: - - '@types/node' - - '@microsoft/tsdoc-config@0.18.0': - dependencies: - '@microsoft/tsdoc': 0.16.0 - ajv: 8.12.0 - jju: 1.4.0 - resolve: 1.22.11 - - '@microsoft/tsdoc@0.16.0': {} - - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.2 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7406,7 +6339,134 @@ snapshots: '@one-ini/wasm@0.1.1': {} - '@oxc-project/types@0.129.0': {} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + + '@oxc-project/types@0.133.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + optional: true + + '@oxc-resolver/binding-android-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + optional: true '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -7452,7 +6512,7 @@ snapshots: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.1.1 - picomatch: 4.0.3 + picomatch: 4.0.4 optionalDependencies: '@parcel/watcher-android-arm64': 2.5.6 '@parcel/watcher-darwin-arm64': 2.5.6 @@ -7469,16 +6529,22 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true + '@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021': + dependencies: + css-select: 7.0.0 + css-tree: 3.2.1 + csso: 5.0.5 + lodash: 4.18.1 + sax: 1.6.0 + + '@penpot/ua-parser@https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4': {} + '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.58.0': + '@playwright/test@1.61.0': dependencies: - playwright: 1.58.0 - - '@playwright/test@1.60.0': - dependencies: - playwright: 1.60.0 + playwright: 1.61.0 '@polka/url@1.0.0-next.29': {} @@ -7533,185 +6599,65 @@ snapshots: '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 '@resvg/resvg-js-win32-x64-msvc': 2.6.2 - '@rolldown/binding-android-arm64@1.0.0': + '@rolldown/binding-android-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-arm64@1.0.0': + '@rolldown/binding-darwin-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-x64@1.0.0': + '@rolldown/binding-darwin-x64@1.0.3': optional: true - '@rolldown/binding-freebsd-x64@1.0.0': + '@rolldown/binding-freebsd-x64@1.0.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0': + '@rolldown/binding-linux-arm64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0': + '@rolldown/binding-linux-arm64-musl@1.0.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0': + '@rolldown/binding-linux-ppc64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0': + '@rolldown/binding-linux-s390x-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0': + '@rolldown/binding-linux-x64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0': + '@rolldown/binding-linux-x64-musl@1.0.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0': + '@rolldown/binding-openharmony-arm64@1.0.3': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0': + '@rolldown/binding-wasm32-wasi@1.0.3': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0': + '@rolldown/binding-win32-arm64-msvc@1.0.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0': + '@rolldown/binding-win32-x64-msvc@1.0.3': optional: true - '@rolldown/pluginutils@1.0.0': {} + '@rolldown/pluginutils@1.0.1': {} - '@rolldown/pluginutils@1.0.0-rc.7': {} - - '@rollup/pluginutils@5.3.0(rollup@4.57.1)': + '@rollup/pluginutils@5.4.0': dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.57.1 - - '@rollup/rollup-android-arm-eabi@4.57.1': - optional: true - - '@rollup/rollup-android-arm64@4.57.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.57.1': - optional: true - - '@rollup/rollup-darwin-x64@4.57.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.57.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.57.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.57.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.57.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.57.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.57.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.57.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.57.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.57.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.57.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.57.1': - optional: true + picomatch: 4.0.4 '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.19.1(@types/node@25.7.0)': - dependencies: - ajv: 8.13.0 - ajv-draft-04: 1.0.0(ajv@8.13.0) - ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.3 - import-lazy: 4.0.0 - jju: 1.4.0 - resolve: 1.22.11 - semver: 7.5.4 - optionalDependencies: - '@types/node': 25.7.0 - - '@rushstack/problem-matcher@0.1.1(@types/node@25.7.0)': - optionalDependencies: - '@types/node': 25.7.0 - - '@rushstack/rig-package@0.6.0': - dependencies: - resolve: 1.22.11 - strip-json-comments: 3.1.1 - - '@rushstack/terminal@0.21.0(@types/node@25.7.0)': - dependencies: - '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) - '@rushstack/problem-matcher': 0.1.1(@types/node@25.7.0) - supports-color: 8.1.1 - optionalDependencies: - '@types/node': 25.7.0 - - '@rushstack/ts-command-line@5.2.0(@types/node@25.7.0)': - dependencies: - '@rushstack/terminal': 0.21.0(@types/node@25.7.0) - '@types/argparse': 1.0.38 - argparse: 1.0.10 - string-argv: 0.3.2 - transitivePeerDependencies: - - '@types/node' - - '@sinclair/typebox@0.27.10': {} - '@sindresorhus/merge-streams@4.0.0': {} '@so-ric/colorspace@1.1.6': @@ -7721,182 +6667,123 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/addon-docs@10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - ts-dedent: 2.2.0 + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) + '@storybook/csf-plugin': 10.4.5(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@storybook/react-dom-shim': 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.3.0 + optionalDependencies: + '@types/react': 19.2.17 transitivePeerDependencies: - - '@types/react' + - '@types/react-dom' - esbuild - rollup - vite - webpack - '@storybook/addon-themes@10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@storybook/addon-themes@10.4.5(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))': dependencies: - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - ts-dedent: 2.2.0 + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.3.0 - '@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.6)(@vitest/browser@4.1.3)(@vitest/runner@4.1.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.6)': + '@storybook/addon-vitest@10.4.5(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(@vitest/runner@4.1.9)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.9)': dependencies: '@storybook/global': 5.0.0 - '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: - '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) - '@vitest/browser-playwright': 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) - '@vitest/runner': 4.1.6 - vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@vitest/browser': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) + '@vitest/browser-playwright': 4.1.9(playwright@1.61.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) + '@vitest/runner': 4.1.9 + vitest: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) transitivePeerDependencies: - react - react-dom - '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/builder-vite@10.4.5(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - ts-dedent: 2.2.0 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + '@storybook/csf-plugin': 10.4.5(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.3.0 + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/csf-plugin@10.4.5(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - ts-dedent: 2.2.0 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - transitivePeerDependencies: - - esbuild - - rollup - - webpack - - '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) unplugin: 2.3.11 optionalDependencies: - esbuild: 0.28.0 - rollup: 4.57.1 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - - '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - unplugin: 2.3.11 - optionalDependencies: - esbuild: 0.28.0 - rollup: 4.57.1 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + esbuild: 0.28.1 + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) '@storybook/global@5.0.0': {} - '@storybook/icons@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@storybook/icons@2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - '@storybook/icons@2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@storybook/react-dom-shim@10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))': dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@storybook/react-dom-shim@10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/react-vite@10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - - '@storybook/react-dom-shim@10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - - '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@storybook/react': 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3) - empathic: 2.0.0 + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.4.5(esbuild@0.28.1)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + '@storybook/react': 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + empathic: 2.0.1 magic-string: 0.30.21 - react: 19.2.3 - react-docgen: 8.0.2 - react-dom: 19.2.3(react@19.2.3) - resolve: 1.22.11 - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.7 + react-docgen: 8.0.3 + react-dom: 19.2.7(react@19.2.7) + resolve: 1.22.12 + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tsconfig-paths: 4.2.0 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' - esbuild - rollup - supports-color - typescript - webpack - '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@storybook/react': 10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3) - empathic: 2.0.0 - magic-string: 0.30.21 - react: 19.2.4 - react-docgen: 8.0.2 - react-dom: 19.2.4(react@19.2.4) - resolve: 1.22.11 - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - tsconfig-paths: 4.2.0 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - transitivePeerDependencies: - - esbuild - - rollup - - supports-color - - typescript - - webpack - - '@storybook/react@10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)': + '@storybook/react@10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) - react: 19.2.3 - react-docgen: 8.0.2 + '@storybook/react-dom-shim': 10.4.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + react: 19.2.7 + react-docgen: 8.0.3 react-docgen-typescript: 2.4.0(typescript@6.0.3) - react-dom: 19.2.3(react@19.2.3) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@storybook/react@10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)': - dependencies: - '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) - react: 19.2.4 - react-docgen: 8.0.2 - react-docgen-typescript: 2.4.0(typescript@6.0.3) - react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) typescript: 6.0.3 transitivePeerDependencies: - supports-color '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.28.6 + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -7906,77 +6793,73 @@ snapshots: '@testing-library/jest-dom@6.9.1': dependencies: - '@adobe/css-tools': 4.4.4 + '@adobe/css-tools': 4.5.0 aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 '@testing-library/dom': 10.4.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: '@testing-library/dom': 10.4.1 - '@tokens-studio/sd-transforms@1.2.11(style-dictionary@5.0.0-rc.1(tslib@2.8.1))': + '@tokens-studio/sd-transforms@2.0.3(style-dictionary@5.4.4(tslib@2.8.1))': dependencies: - '@bundled-es-modules/deepmerge': 4.3.1 + '@bundled-es-modules/deepmerge': 4.3.2 '@bundled-es-modules/postcss-calc-ast-parser': 0.1.6 '@tokens-studio/types': 0.5.2 - colorjs.io: 0.4.5 - expr-eval-fork: 2.0.2 + colorjs.io: 0.5.2 + expr-eval-fork: 3.0.3 is-mergeable-object: 1.1.1 - style-dictionary: 5.0.0-rc.1(tslib@2.8.1) + style-dictionary: 5.4.4(tslib@2.8.1) '@tokens-studio/tokenscript-interpreter@0.26.0': dependencies: - arktype: 2.1.29 + arktype: 2.2.0 commander: 14.0.3 readline-sync: 1.4.10 - yauzl: 3.2.0 + yauzl: 3.4.0 '@tokens-studio/types@0.5.2': {} - '@trysound/sax@0.2.0': {} - '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true - '@types/argparse@1.0.38': {} - '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/chai@5.2.3': dependencies: @@ -7987,33 +6870,23 @@ snapshots: '@types/doctrine@0.0.9': {} - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} - '@types/mdx@2.0.13': {} + '@types/mdx@2.0.14': {} - '@types/node@22.19.9': + '@types/node@25.9.3': dependencies: - undici-types: 6.21.0 + undici-types: 7.24.6 - '@types/node@25.2.1': + '@types/react-dom@19.2.3(@types/react@19.2.17)': dependencies: - undici-types: 7.16.0 + '@types/react': 19.2.17 - '@types/node@25.7.0': - dependencies: - undici-types: 7.21.0 - - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': + '@types/react@19.2.17': dependencies: csstype: 3.2.3 @@ -8021,109 +6894,58 @@ snapshots: '@types/triple-beam@1.3.5': {} - '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.2(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) optionalDependencies: babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-playwright@4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': + '@vitest/browser-playwright@4.1.9(playwright@1.61.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9)': dependencies: - '@vitest/browser': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) - '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - playwright: 1.60.0 + '@vitest/browser': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) + '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + playwright: 1.61.0 tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@1.6.1(playwright@1.58.0)(vitest@1.6.1)': - dependencies: - '@vitest/utils': 1.6.1 - magic-string: 0.30.21 - sirv: 2.0.4 - vitest: 1.6.1(@types/node@25.2.1)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - optionalDependencies: - playwright: 1.58.0 - - '@vitest/browser@4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': + '@vitest/browser@4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@vitest/utils': 4.1.3 + '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - ws: 8.19.0 + vitest: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + ws: 8.21.0 transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': - dependencies: - '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@vitest/utils': 4.1.6 - magic-string: 0.30.21 - pngjs: 7.0.0 - sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - ws: 8.19.0 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - - '@vitest/coverage-v8@1.6.1(vitest@1.6.1)': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.3(supports-color@5.5.0) - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - picocolors: 1.1.1 - std-env: 3.10.0 - strip-literal: 2.1.1 - test-exclude: 6.0.0 - vitest: 1.6.1(@types/node@25.2.1)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - transitivePeerDependencies: - - supports-color - - '@vitest/coverage-v8@4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6)': + '@vitest/coverage-v8@4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.3 - ast-v8-to-istanbul: 1.0.0 + '@vitest/utils': 4.1.9 + ast-v8-to-istanbul: 1.0.4 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 + magicast: 0.5.3 + obug: 2.1.3 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) optionalDependencies: - '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) - - '@vitest/expect@1.6.1': - dependencies: - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - chai: 4.5.0 + '@vitest/browser': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) '@vitest/expect@3.2.4': dependencies: @@ -8133,96 +6955,59 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.6': + '@vitest/expect@4.1.9': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.6 - '@vitest/utils': 4.1.6 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))': dependencies: - '@vitest/spy': 4.1.3 + '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - - '@vitest/mocker@4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 4.1.6 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.1.3': + '@vitest/pretty-format@4.1.9': dependencies: tinyrainbow: 3.1.0 - '@vitest/pretty-format@4.1.6': + '@vitest/runner@4.1.9': dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@1.6.1': - dependencies: - '@vitest/utils': 1.6.1 - p-limit: 5.0.0 - pathe: 1.1.2 - - '@vitest/runner@4.1.6': - dependencies: - '@vitest/utils': 4.1.6 + '@vitest/utils': 4.1.9 pathe: 2.0.3 - '@vitest/snapshot@1.6.1': + '@vitest/snapshot@4.1.9': dependencies: - magic-string: 0.30.21 - pathe: 1.1.2 - pretty-format: 29.7.0 - - '@vitest/snapshot@4.1.6': - dependencies: - '@vitest/pretty-format': 4.1.6 - '@vitest/utils': 4.1.6 + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@1.6.1': - dependencies: - tinyspy: 2.2.1 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.3': {} + '@vitest/spy@4.1.9': {} - '@vitest/spy@4.1.6': {} - - '@vitest/ui@1.6.1(vitest@1.6.1)': + '@vitest/ui@4.1.9(vitest@4.1.9)': dependencies: - '@vitest/utils': 1.6.1 - fast-glob: 3.3.3 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 1.1.2 - picocolors: 1.1.1 - sirv: 2.0.4 - vitest: 1.6.1(@types/node@25.2.1)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - - '@vitest/utils@1.6.1': - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/utils': 4.1.9 + fflate: 0.8.3 + flatted: 3.4.2 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vitest: 4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) '@vitest/utils@3.2.4': dependencies: @@ -8230,15 +7015,9 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.1.3': + '@vitest/utils@4.1.9': dependencies: - '@vitest/pretty-format': 4.1.3 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@vitest/utils@4.1.6': - dependencies: - '@vitest/pretty-format': 4.1.6 + '@vitest/pretty-format': 4.1.9 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -8254,44 +7033,9 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.27': - dependencies: - '@babel/parser': 7.29.0 - '@vue/shared': 3.5.27 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.27': - dependencies: - '@vue/compiler-core': 3.5.27 - '@vue/shared': 3.5.27 - - '@vue/compiler-vue2@2.7.16': - dependencies: - de-indent: 1.0.2 - he: 1.2.0 - - '@vue/language-core@2.2.0(typescript@6.0.3)': - dependencies: - '@volar/language-core': 2.4.28 - '@vue/compiler-dom': 3.5.27 - '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.27 - alien-signals: 0.4.14 - minimatch: 9.0.5 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - optionalDependencies: - typescript: 6.0.3 - - '@vue/shared@3.5.27': {} - '@webcontainer/env@1.1.1': {} - '@xmldom/xmldom@0.8.11': {} - - '@yarnpkg/lockfile@1.1.0': {} + '@xmldom/xmldom@0.8.13': {} '@zip.js/zip.js@2.8.26(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95)': {} @@ -8302,34 +7046,18 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.16.0): + acorn-jsx@5.3.2(acorn@8.17.0): dependencies: - acorn: 8.16.0 + acorn: 8.17.0 - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 + acorn@8.17.0: {} - acorn@8.15.0: {} - - acorn@8.16.0: {} - - agent-base@6.0.2: + agent-base@6.0.2(supports-color@5.5.0): dependencies: debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color - agent-base@7.1.4: {} - - ajv-draft-04@1.0.0(ajv@8.13.0): - optionalDependencies: - ajv: 8.13.0 - - ajv-formats@3.0.1(ajv@8.13.0): - optionalDependencies: - ajv: 8.13.0 - ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -8337,21 +7065,12 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.12.0: + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 - - ajv@8.13.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - - alien-signals@0.4.14: {} ansi-regex@5.0.1: {} @@ -8372,11 +7091,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 + picomatch: 2.3.2 argparse@2.0.1: {} @@ -8390,7 +7105,7 @@ snapshots: dependencies: '@ark/util': 0.56.0 - arktype@2.1.29: + arktype@2.2.0: dependencies: '@ark/schema': 0.56.0 '@ark/util': 0.56.0 @@ -8403,62 +7118,62 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -8467,14 +7182,12 @@ snapshots: assert@2.1.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 is-nan: 1.3.2 object-is: 1.1.6 object.assign: 4.1.7 util: 0.12.5 - assertion-error@1.1.0: {} - assertion-error@2.0.1: {} ast-types-flow@0.0.8: {} @@ -8483,7 +7196,7 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@1.0.0: + ast-v8-to-istanbul@1.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -8497,32 +7210,26 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.5.0(postcss@8.5.14): + autoprefixer@10.5.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001792 + caniuse-lite: 1.0.30001799 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.14 + postcss: 8.5.15 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.1: {} + axe-core@4.12.1: {} - axios@0.26.1: - dependencies: - follow-redirects: 1.15.11 - transitivePeerDependencies: - - debug - - axios@1.16.1: + axios@1.17.0(supports-color@5.5.0): dependencies: follow-redirects: 1.16.0 form-data: 4.0.5 - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@5.5.0) proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -8532,7 +7239,7 @@ snapshots: babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 balanced-match@1.0.2: {} @@ -8540,9 +7247,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.29: {} - - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.36: {} bidi-js@1.0.3: dependencies: @@ -8550,14 +7255,6 @@ snapshots: binary-extensions@2.3.0: {} - binary-install@1.1.2: - dependencies: - axios: 0.26.1 - rimraf: 3.0.2 - tar: 6.2.1 - transitivePeerDependencies: - - debug - bintrees@1.0.2: {} bl@4.1.0: @@ -8566,7 +7263,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@2.2.2: + body-parser@2.2.2(supports-color@5.5.0): dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -8574,25 +7271,22 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.2 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color boolbase@1.0.0: {} - brace-expansion@1.1.12: + boolbase@2.0.0: {} + + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@1.1.13: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: + brace-expansion@2.1.1: dependencies: balanced-match: 1.0.2 @@ -8604,24 +7298,14 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 - electron-to-chromium: 1.5.286 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.29 - caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.355 - node-releases: 2.0.44 + baseline-browser-mapping: 2.10.36 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.372 + node-releases: 2.0.47 update-browserslist-db: 1.2.3(browserslist@4.28.2) - buffer-crc32@0.2.13: {} - buffer-from@1.1.2: {} buffer@5.7.1: @@ -8640,11 +7324,9 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} - cacheable@2.3.5: dependencies: - '@cacheable/memory': 2.0.8 + '@cacheable/memory': 2.0.9 '@cacheable/utils': 2.4.1 hookified: 1.15.1 keyv: 5.6.0 @@ -8655,7 +7337,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -8669,25 +7351,13 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001799: {} - caniuse-lite@1.0.30001792: {} - - canvas@3.2.1: + canvas@3.2.3: dependencies: node-addon-api: 7.1.1 prebuild-install: 7.1.3 - chai@4.5.0: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 - chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -8713,10 +7383,6 @@ snapshots: change-case@5.4.4: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - check-error@2.1.3: {} chokidar@3.6.0: @@ -8731,16 +7397,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chokidar@4.0.3: + chokidar@5.0.0: dependencies: - readdirp: 4.1.2 + readdirp: 5.0.0 chownr@1.1.4: {} - chownr@2.0.0: {} - - ci-info@3.9.0: {} - clean-css@4.2.4: dependencies: source-map: 0.6.1 @@ -8751,6 +7413,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + clone-buffer@1.0.0: {} clone-stats@1.0.0: {} @@ -8796,8 +7464,6 @@ snapshots: colord@2.9.3: {} - colorjs.io@0.4.5: {} - colorjs.io@0.5.2: {} combined-stream@1.0.8: @@ -8834,43 +7500,45 @@ snapshots: concat-map@0.0.1: {} - concurrently@9.2.1: + concurrently@10.0.3: dependencies: - chalk: 4.1.2 + chalk: 5.6.2 rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 + shell-quote: 1.8.4 + supports-color: 10.2.2 tree-kill: 1.2.2 - yargs: 17.7.2 + yargs: 18.0.0 confbox@0.1.8: {} - confbox@0.2.2: {} + confbox@0.2.4: {} config-chain@1.1.13: dependencies: ini: 1.3.8 proto-list: 1.2.4 - content-disposition@1.0.1: {} + content-disposition@1.1.0: {} content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.2.2: {} cookie@0.7.2: {} - core-js-pure@3.48.0: {} + core-js-pure@3.49.0: {} core-util-is@1.0.3: {} - cosmiconfig@9.0.1(typescript@6.0.3): + cosmiconfig@9.0.2(typescript@6.0.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: typescript: 6.0.3 @@ -8905,13 +7573,13 @@ snapshots: domutils: 2.8.0 nth-check: 2.1.1 - css-select@5.2.2: + css-select@7.0.0: dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 + boolbase: 2.0.0 + css-what: 8.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + nth-check: 3.0.1 css-selector-parser@1.4.1: {} @@ -8925,11 +7593,6 @@ snapshots: mdn-data: 2.0.28 source-map-js: 1.2.1 - css-tree@3.1.0: - dependencies: - mdn-data: 2.12.2 - source-map-js: 1.2.1 - css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -8937,6 +7600,8 @@ snapshots: css-what@6.2.2: {} + css-what@8.0.0: {} + css.escape@1.5.1: {} cssesc@3.0.0: {} @@ -8951,22 +7616,10 @@ snapshots: cssom@0.5.0: {} - cssstyle@5.3.7: - dependencies: - '@asamuzakjp/css-color': 4.1.2 - '@csstools/css-syntax-patches-for-csstree': 1.0.26 - css-tree: 3.1.0 - lru-cache: 11.2.5 - csstype@3.2.3: {} damerau-levenshtein@1.0.8: {} - data-urls@6.0.1: - dependencies: - whatwg-mimetype: 5.0.0 - whatwg-url: 15.1.0 - data-urls@7.0.0: dependencies: whatwg-mimetype: 5.0.0 @@ -8992,9 +7645,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - date-fns@4.1.0: {} - - de-indent@1.0.2: {} + date-fns@4.4.0: {} debug@2.6.9: dependencies: @@ -9016,10 +7667,6 @@ snapshots: dependencies: mimic-response: 3.1.0 - deep-eql@4.1.4: - dependencies: - type-detect: 4.1.0 - deep-eql@5.0.2: {} deep-extend@0.6.0: {} @@ -9055,16 +7702,10 @@ snapshots: dequal@2.0.3: {} - detect-europe-js@0.1.2: {} - detect-libc@2.1.2: {} dettle@1.0.5: {} - diff-sequences@29.6.3: {} - - diff@8.0.3: {} - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -9079,7 +7720,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 csstype: 3.2.3 dom-serializer@1.4.1: @@ -9088,21 +7729,23 @@ snapshots: domhandler: 4.3.1 entities: 2.2.0 - dom-serializer@2.0.0: + dom-serializer@3.1.1: dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.0.0 domelementtype@2.3.0: {} + domelementtype@3.0.0: {} + domhandler@4.3.1: dependencies: domelementtype: 2.3.0 - domhandler@5.0.3: + domhandler@6.0.1: dependencies: - domelementtype: 2.3.0 + domelementtype: 3.0.0 domutils@2.8.0: dependencies: @@ -9110,19 +7753,19 @@ snapshots: domelementtype: 2.3.0 domhandler: 4.3.1 - domutils@3.2.2: + domutils@4.0.2: dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 - draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: fbjs: 3.0.5(encoding@0.1.13) - immutable: 3.7.6 + immutable: 3.8.3 object-assign: 4.1.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) transitivePeerDependencies: - encoding @@ -9134,24 +7777,24 @@ snapshots: eastasianwidth@0.2.0: {} - editorconfig@1.0.4: + editorconfig@1.0.7: dependencies: '@one-ini/wasm': 0.1.1 commander: 10.0.1 - minimatch: 9.0.1 - semver: 7.7.4 + minimatch: 9.0.9 + semver: 7.8.4 ee-first@1.1.1: {} - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.372: {} - electron-to-chromium@1.5.355: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - empathic@2.0.0: {} + empathic@2.0.1: {} enabled@2.0.0: {} @@ -9167,12 +7810,6 @@ snapshots: entities@2.2.0: {} - entities@4.5.0: {} - - entities@6.0.1: {} - - entities@7.0.1: {} - entities@8.0.0: {} env-paths@2.2.1: {} @@ -9181,22 +7818,22 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.24.1: + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -9205,7 +7842,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -9223,31 +7860,31 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.2.2: + es-iterator-helpers@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -9259,11 +7896,11 @@ snapshots: has-symbols: 1.1.0 internal-slot: 1.1.0 iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 + math-intrinsics: 1.1.0 es-module-lexer@2.1.0: {} - es-object-atoms@1.1.1: + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -9272,11 +7909,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 es-to-primitive@1.3.0: dependencies: @@ -9284,118 +7921,63 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.21.5: + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 - esbuild@0.27.3: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - - esbuild@0.27.4: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.4 - '@esbuild/android-arm': 0.27.4 - '@esbuild/android-arm64': 0.27.4 - '@esbuild/android-x64': 0.27.4 - '@esbuild/darwin-arm64': 0.27.4 - '@esbuild/darwin-x64': 0.27.4 - '@esbuild/freebsd-arm64': 0.27.4 - '@esbuild/freebsd-x64': 0.27.4 - '@esbuild/linux-arm': 0.27.4 - '@esbuild/linux-arm64': 0.27.4 - '@esbuild/linux-ia32': 0.27.4 - '@esbuild/linux-loong64': 0.27.4 - '@esbuild/linux-mips64el': 0.27.4 - '@esbuild/linux-ppc64': 0.27.4 - '@esbuild/linux-riscv64': 0.27.4 - '@esbuild/linux-s390x': 0.27.4 - '@esbuild/linux-x64': 0.27.4 - '@esbuild/netbsd-arm64': 0.27.4 - '@esbuild/netbsd-x64': 0.27.4 - '@esbuild/openbsd-arm64': 0.27.4 - '@esbuild/openbsd-x64': 0.27.4 - '@esbuild/openharmony-arm64': 0.27.4 - '@esbuild/sunos-x64': 0.27.4 - '@esbuild/win32-arm64': 0.27.4 - '@esbuild/win32-ia32': 0.27.4 - '@esbuild/win32-x64': 0.27.4 - - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 escalade@3.2.0: {} @@ -9405,24 +7987,24 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.11 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): + eslint-module-utils@2.13.0(eslint-import-resolver-node@0.3.10)(eslint@9.39.4): dependencies: debug: 3.2.7 optionalDependencies: - eslint: 9.39.2 - eslint-import-resolver-node: 0.3.9 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(eslint@9.39.2): + eslint-plugin-import@2.32.0(eslint@9.39.4): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9431,36 +8013,36 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.2 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) - hasown: 2.0.2 - is-core-module: 2.16.1 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.13.0(eslint-import-resolver-node@0.3.10)(eslint@9.39.4) + hasown: 2.0.4 + is-core-module: 2.16.2 is-glob: 4.0.3 minimatch: 3.1.5 object.fromentries: 2.0.8 object.groupby: 1.0.3 object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: 1.0.9 + string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.11.1 + axe-core: 4.12.1 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.2 - hasown: 2.0.2 + eslint: 9.39.4 + hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 minimatch: 3.1.5 @@ -9468,35 +8050,35 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.0.1(eslint@9.39.2): + eslint-plugin-react-hooks@7.1.1(eslint@9.39.4): dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 - eslint: 9.39.2 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/parser': 7.29.7 + eslint: 9.39.4 hermes-parser: 0.25.1 - zod: 4.3.6 - zod-validation-error: 4.0.2(zod@4.3.6) + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.2): + eslint-plugin-react@7.37.5(eslint@9.39.4): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.2.2 - eslint: 9.39.2 + es-iterator-helpers: 1.3.3 + eslint: 9.39.4 estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.4 jsx-ast-utils: 3.3.5 minimatch: 3.1.5 object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.5 + resolve: 2.0.0-next.7 semver: 6.3.1 string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 @@ -9510,15 +8092,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2: + eslint@9.39.4: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.2 + '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 @@ -9551,8 +8133,8 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -9571,7 +8153,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -9579,31 +8161,19 @@ snapshots: events@3.3.0: {} - eventsource-parser@3.0.8: {} - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + eventsource-parser@3.1.0: {} expand-template@2.0.3: {} expect-type@1.3.0: {} - expr-eval-fork@2.0.2: {} + expr-eval-fork@3.0.3: {} - express@5.2.1: + express@5.2.1(supports-color@5.5.0): dependencies: accepts: 2.0.0 - body-parser: 2.2.2 - content-disposition: 1.0.1 + body-parser: 2.2.2(supports-color@5.5.0) + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -9612,7 +8182,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@5.5.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -9621,13 +8191,13 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.2 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 + router: 2.2.0(supports-color@5.5.0) + send: 1.2.1(supports-color@5.5.0) serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -9652,6 +8222,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.2: {} + fastest-levenshtein@1.0.16: {} fastq@1.20.1: @@ -9672,17 +8244,13 @@ snapshots: transitivePeerDependencies: - encoding - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 fecha@4.2.3: {} - fflate@0.8.2: {} + fflate@0.8.3: {} file-entry-cache@11.1.3: dependencies: @@ -9696,7 +8264,7 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@5.5.0): dependencies: debug: 4.4.3(supports-color@5.5.0) encodeurl: 2.0.0 @@ -9712,10 +8280,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root@2.0.0: - dependencies: - micromatch: 4.0.8 - flat-cache@4.0.1: dependencies: flatted: 3.4.2 @@ -9727,14 +8291,10 @@ snapshots: flatted: 3.4.2 hookified: 1.15.1 - flatted@3.3.3: {} - flatted@3.4.2: {} fn.name@1.1.0: {} - follow-redirects@1.15.11: {} - follow-redirects@1.16.0: {} for-each@0.3.5: @@ -9751,7 +8311,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.4 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -9762,22 +8322,6 @@ snapshots: fs-constants@1.0.0: {} - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs-extra@11.3.3: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -9788,14 +8332,17 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.8: + function.prototype.name@1.2.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 - define-properties: 1.2.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 functions-have-names: 1.2.3 - hasown: 2.0.2 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 is-callable: 1.2.7 + is-document.all: 1.0.0 functions-have-names@1.2.3: {} @@ -9811,27 +8358,23 @@ snapshots: get-east-asian-width@1.6.0: {} - get-func-name@2.0.2: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@8.0.1: {} + es-object-atoms: 1.1.2 get-symbol-description@1.1.0: dependencies: @@ -9864,17 +8407,11 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@13.0.1: - dependencies: - minimatch: 10.1.2 - minipass: 7.1.2 - path-scurry: 2.0.1 - glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -9886,7 +8423,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -9948,12 +8485,10 @@ snapshots: dependencies: hookified: 1.15.1 - hasown@2.0.2: + hasown@2.0.4: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -9970,7 +8505,7 @@ snapshots: html-encoding-sniffer@6.0.0: dependencies: - '@exodus/bytes': 1.11.0 + '@exodus/bytes': 1.15.1 transitivePeerDependencies: - '@noble/hashes' @@ -9986,29 +8521,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: + https-proxy-agent@5.0.1(supports-color@5.5.0): dependencies: - agent-base: 7.1.4 + agent-base: 6.0.2(supports-color@5.5.0) debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - human-signals@5.0.0: {} - hyperdyperid@1.2.0: {} iconv-lite@0.6.3: @@ -10019,9 +8538,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.14): + icss-utils@5.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.14 + postcss: 8.5.15 ieee754@1.2.1: {} @@ -10031,19 +8550,15 @@ snapshots: ignore@7.0.5: {} - immutable@3.7.6: {} + immutable@3.8.3: {} - immutable@5.1.4: {} - - immutable@5.1.5: {} + immutable@5.1.6: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-lazy@4.0.0: {} - import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -10064,8 +8579,8 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + hasown: 2.0.4 + side-channel: 1.1.1 ipaddr.js@1.9.1: {} @@ -10076,7 +8591,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -10105,9 +8620,9 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -10120,10 +8635,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-docker@2.2.1: {} - is-docker@3.0.0: {} + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -10154,7 +8671,7 @@ snapshots: is-nan@1.3.2: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 is-negative-zero@2.0.3: {} @@ -10181,7 +8698,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 is-set@2.0.3: {} @@ -10189,12 +8706,8 @@ snapshots: dependencies: call-bound: 1.0.4 - is-standalone-pwa@0.1.1: {} - is-stream@2.0.1: {} - is-stream@3.0.0: {} - is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -10208,7 +8721,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 is-weakmap@2.0.2: {} @@ -10221,11 +8734,7 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - is-wsl@3.1.0: + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -10243,14 +8752,6 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3(supports-color@5.5.0) - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 @@ -10259,7 +8760,7 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 @@ -10271,93 +8772,59 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jju@1.4.0: {} - joi@18.2.1: dependencies: '@hapi/address': 5.1.1 '@hapi/formula': 3.0.2 '@hapi/hoek': 11.0.7 '@hapi/pinpoint': 2.0.1 - '@hapi/tlds': 1.1.4 + '@hapi/tlds': 1.1.7 '@hapi/topo': 6.0.2 '@standard-schema/spec': 1.1.0 js-beautify@1.15.4: dependencies: config-chain: 1.1.13 - editorconfig: 1.0.4 + editorconfig: 1.0.7 glob: 10.5.0 - js-cookie: 3.0.5 + js-cookie: 3.0.8 nopt: 7.2.1 - js-cookie@3.0.5: {} + js-cookie@3.0.8: {} js-tokens@10.0.0: {} js-tokens@4.0.0: {} - js-tokens@9.0.1: {} - - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 - jsdom@27.4.0(canvas@3.2.1): - dependencies: - '@acemir/cssom': 0.9.31 - '@asamuzakjp/dom-selector': 6.7.8 - '@exodus/bytes': 1.11.0 - cssstyle: 5.3.7 - data-urls: 6.0.1 - decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 6.0.0 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 8.0.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 15.1.0 - ws: 8.19.0 - xml-name-validator: 5.0.0 - optionalDependencies: - canvas: 3.2.1 - transitivePeerDependencies: - - '@noble/hashes' - - bufferutil - - supports-color - - utf-8-validate - - jsdom@29.1.1(canvas@3.2.1): + jsdom@29.1.1(canvas@3.2.3): dependencies: '@asamuzakjp/css-color': 5.1.11 '@asamuzakjp/dom-selector': 7.1.1 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) - '@exodus/bytes': 1.15.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 css-tree: 3.2.1 data-urls: 7.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.3.6 + lru-cache: 11.5.1 parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.1 - undici: 7.25.0 + undici: 7.27.2 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 whatwg-url: 16.0.1 xml-name-validator: 5.0.0 optionalDependencies: - canvas: 3.2.1 + canvas: 3.2.3 transitivePeerDependencies: - '@noble/hashes' @@ -10375,28 +8842,12 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stable-stringify@1.3.0: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - isarray: 2.0.5 - jsonify: 0.0.1 - object-keys: 1.1.1 - json5@1.0.2: dependencies: minimist: 1.2.8 json5@2.2.3: {} - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jsonify@0.0.1: {} - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -10414,10 +8865,6 @@ snapshots: kind-of@6.0.3: {} - klaw-sync@6.0.0: - dependencies: - graceful-fs: 4.2.11 - known-css-properties@0.37.0: {} kolorist@1.8.0: {} @@ -10495,15 +8942,10 @@ snapshots: loader-utils@3.3.1: {} - local-pkg@0.5.1: + local-pkg@1.2.1: dependencies: - mlly: 1.8.0 - pkg-types: 1.3.1 - - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 + mlly: 1.8.2 + pkg-types: 2.3.1 quansync: 0.2.11 locate-path@6.0.0: @@ -10520,8 +8962,6 @@ snapshots: lodash.truncate@4.4.2: {} - lodash@4.17.23: {} - lodash@4.18.1: {} logform@2.7.0: @@ -10537,51 +8977,35 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - loupe@3.2.1: {} lru-cache@10.4.3: {} - lru-cache@11.2.5: {} - - lru-cache@11.3.6: {} + lru-cache@11.5.1: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - lz-string@1.5.0: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.3: dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - - magicast@0.5.2: - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.4 map-stream@0.0.7: {} - marked@17.0.6: {} + marked@18.0.5: {} math-intrinsics@1.1.0: {} @@ -10591,26 +9015,24 @@ snapshots: mdn-data@2.0.28: {} - mdn-data@2.12.2: {} - mdn-data@2.27.1: {} media-typer@1.1.0: {} - memfs@4.56.10(tslib@2.8.1): + memfs@4.57.7(tslib@2.8.1): dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.7(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.7(tslib@2.8.1) '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -10620,14 +9042,12 @@ snapshots: merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.52.0: {} @@ -10641,65 +9061,36 @@ snapshots: dependencies: mime-db: 1.54.0 - mimic-fn@4.0.0: {} - mimic-response@3.1.0: {} min-indent@1.0.1: {} - minimatch@10.1.2: - dependencies: - '@isaacs/brace-expansion': 5.0.1 - minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - minimatch@3.1.5: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.15 - minimatch@9.0.1: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.1 minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - - minipass@7.1.2: {} - minipass@7.1.3: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - mkdirp-classic@0.5.3: {} - mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mlly@1.8.0: + mlly@1.8.2: dependencies: - acorn: 8.15.0 + acorn: 8.17.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.3 + ufo: 1.6.4 mrmime@2.0.1: {} @@ -10707,11 +9098,9 @@ snapshots: ms@2.1.3: {} - muggle-string@0.4.1: {} - mustache@4.2.0: {} - nanoid@3.3.11: {} + nanoid@3.3.12: {} napi-build-utils@2.0.0: {} @@ -10723,21 +9112,26 @@ snapshots: nice-try@1.0.5: {} - node-abi@3.87.0: + node-abi@3.92.0: dependencies: - semver: 7.7.4 + semver: 7.8.4 node-addon-api@7.1.1: {} + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 - node-releases@2.0.27: {} - - node-releases@2.0.44: {} + node-releases@2.0.47: {} nodemon@3.1.14: dependencies: @@ -10746,7 +9140,7 @@ snapshots: ignore-by-default: 1.0.1 minimatch: 10.2.5 pstree.remy: 1.1.8 - semver: 7.7.4 + semver: 7.8.4 simple-update-notifier: 2.0.0 supports-color: 5.5.0 touch: 3.1.1 @@ -10759,7 +9153,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.11 + resolve: 1.22.12 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -10771,68 +9165,68 @@ snapshots: chalk: 2.4.2 cross-spawn: 6.0.6 memorystream: 0.3.1 - minimatch: 3.1.2 + minimatch: 3.1.5 pidtree: 0.3.1 read-pkg: 3.0.0 - shell-quote: 1.8.3 + shell-quote: 1.8.4 string.prototype.padend: 3.1.6 - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 + nth-check@3.0.1: + dependencies: + boolbase: 2.0.0 + object-assign@4.1.1: {} object-inspect@1.13.4: {} object-is@1.1.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 object-keys@1.1.1: {} object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 object.groupby@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 - obug@2.1.1: {} + obug@2.1.3: {} on-finished@2.4.1: dependencies: @@ -10848,10 +9242,6 @@ snapshots: dependencies: fn.name: 1.1.0 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - open@10.2.0: dependencies: default-browser: 5.5.0 @@ -10859,15 +9249,7 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 - open@7.4.2: - dependencies: - is-docker: 2.2.1 - is-wsl: 2.2.0 - - opentype.js@1.3.4: - dependencies: - string.prototype.codepointat: 0.2.1 - tiny-inflate: 1.0.3 + opentype.js@2.0.0: {} optionator@0.9.4: dependencies: @@ -10884,14 +9266,57 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.20.0: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.20.0 + '@oxc-resolver/binding-android-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-x64': 11.20.0 + '@oxc-resolver/binding-freebsd-x64': 11.20.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.20.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.20.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-musl': 11.20.0 + '@oxc-resolver/binding-openharmony-arm64': 11.20.0 + '@oxc-resolver/binding-wasm32-wasi': 11.20.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.20.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.20.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.2.2 - p-limit@7.3.0: dependencies: yocto-queue: 1.2.2 @@ -10913,38 +9338,17 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse5@8.0.0: - dependencies: - entities: 6.0.1 - parse5@8.0.1: dependencies: entities: 8.0.0 parseurl@1.3.3: {} - patch-package@8.0.1: - dependencies: - '@yarnpkg/lockfile': 1.1.0 - chalk: 4.1.2 - ci-info: 3.9.0 - cross-spawn: 7.0.6 - find-yarn-workspace-root: 2.0.0 - fs-extra: 10.1.0 - json-stable-stringify: 1.3.0 - klaw-sync: 6.0.0 - minimist: 1.2.8 - open: 7.4.2 - semver: 7.7.4 - slash: 2.0.0 - tmp: 0.2.5 - yaml: 2.8.2 - path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -10955,26 +9359,19 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 - - path-scurry@2.0.1: - dependencies: - lru-cache: 11.2.5 - minipass: 7.1.2 + minipass: 7.1.3 path-scurry@2.0.2: dependencies: - lru-cache: 11.2.5 + lru-cache: 11.5.1 minipass: 7.1.3 - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.2: {} path-type@3.0.0: dependencies: @@ -10987,21 +9384,15 @@ snapshots: process: 0.11.10 util: 0.10.4 - pathe@1.1.2: {} - pathe@2.0.3: {} - pathval@1.1.1: {} - pathval@2.0.1: {} pend@1.2.0: {} picocolors@1.1.1: {} - picomatch@2.3.1: {} - - picomatch@4.0.3: {} + picomatch@2.3.2: {} picomatch@4.0.4: {} @@ -11012,28 +9403,20 @@ snapshots: pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.2 pathe: 2.0.3 - pkg-types@2.3.0: + pkg-types@2.3.1: dependencies: - confbox: 0.2.2 + confbox: 0.2.4 exsolve: 1.0.8 pathe: 2.0.3 - playwright-core@1.58.0: {} + playwright-core@1.61.0: {} - playwright-core@1.60.0: {} - - playwright@1.58.0: + playwright@1.61.0: dependencies: - playwright-core: 1.58.0 - optionalDependencies: - fsevents: 2.3.2 - - playwright@1.60.0: - dependencies: - playwright-core: 1.60.0 + playwright-core: 1.61.0 optionalDependencies: fsevents: 2.3.2 @@ -11048,54 +9431,54 @@ snapshots: postcss-clean@1.2.2: dependencies: clean-css: 4.2.4 - postcss: 6.0.23 + postcss: 8.5.15 postcss-media-query-parser@0.2.3: {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.14): + postcss-modules-extract-imports@3.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.14 + postcss: 8.5.15 - postcss-modules-local-by-default@4.2.0(postcss@8.5.14): + postcss-modules-local-by-default@4.2.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.14) - postcss: 8.5.14 - postcss-selector-parser: 7.1.1 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.14): + postcss-modules-scope@3.2.1(postcss@8.5.15): dependencies: - postcss: 8.5.14 - postcss-selector-parser: 7.1.1 + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 - postcss-modules-values@4.0.0(postcss@8.5.14): + postcss-modules-values@4.0.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.14) - postcss: 8.5.14 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-modules@6.0.1(postcss@8.5.14): + postcss-modules@6.0.1(postcss@8.5.15): dependencies: generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.5.14) + icss-utils: 5.1.0(postcss@8.5.15) lodash.camelcase: 4.3.0 - postcss: 8.5.14 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.14) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.14) - postcss-modules-scope: 3.2.1(postcss@8.5.14) - postcss-modules-values: 4.0.0(postcss@8.5.14) + postcss: 8.5.15 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) + postcss-modules-scope: 3.2.1(postcss@8.5.15) + postcss-modules-values: 4.0.0(postcss@8.5.15) string-hash: 1.1.3 postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@7.0.1(postcss@8.5.14): + postcss-safe-parser@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.14 + postcss: 8.5.15 - postcss-scss@4.0.9(postcss@8.5.14): + postcss-scss@4.0.9(postcss@8.5.15): dependencies: - postcss: 8.5.14 + postcss: 8.5.15 - postcss-selector-parser@7.1.1: + postcss-selector-parser@7.1.4: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -11104,15 +9487,9 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@6.0.23: + postcss@8.5.15: dependencies: - chalk: 2.4.2 - source-map: 0.6.1 - supports-color: 5.5.0 - - postcss@8.5.14: - dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -11124,8 +9501,8 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.87.0 - pump: 3.0.3 + node-abi: 3.92.0 + pump: 3.0.4 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.4 @@ -11133,7 +9510,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.1: {} + prettier@3.8.4: {} pretty-format@27.5.1: dependencies: @@ -11141,12 +9518,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - pretty-time@1.1.0: {} prettysize@2.0.0: {} @@ -11182,7 +9553,7 @@ snapshots: pstree.remy@1.1.8: {} - pump@3.0.3: + pump@3.0.4: dependencies: end-of-stream: 1.4.5 once: 1.4.0 @@ -11195,9 +9566,9 @@ snapshots: dependencies: hookified: 2.2.0 - qs@6.14.1: + qs@6.15.2: dependencies: - side-channel: 1.1.0 + side-channel: 1.1.1 quansync@0.2.11: {} @@ -11221,65 +9592,56 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-compiler-runtime@1.0.0(react@19.2.3): + react-compiler-runtime@1.0.0(react@19.2.7): dependencies: - react: 19.2.3 + react: 19.2.7 react-docgen-typescript@2.4.0(typescript@6.0.3): dependencies: typescript: 6.0.3 - react-docgen@8.0.2: + react-docgen@8.0.3: dependencies: - '@babel/core': 7.29.0 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/core': 7.29.7(supports-color@5.5.0) + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 '@types/doctrine': 0.0.9 '@types/resolve': 1.20.6 doctrine: 3.0.0 - resolve: 1.22.11 + resolve: 1.22.12 strip-indent: 4.1.1 transitivePeerDependencies: - supports-color - react-dom@19.2.3(react@19.2.3): + react-dom@19.2.7(react@19.2.7): dependencies: - react: 19.2.3 + react: 19.2.7 scheduler: 0.27.0 - react-dom@19.2.4(react@19.2.4): + react-error-boundary@6.1.2(react@19.2.7): dependencies: - react: 19.2.4 - scheduler: 0.27.0 - - react-error-boundary@6.1.1(react@19.2.4): - dependencies: - react: 19.2.4 + react: 19.2.7 react-is@16.13.1: {} react-is@17.0.2: {} - react-is@18.3.1: {} - react-lifecycles-compat@3.0.4: {} - react-virtualized@9.22.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-virtualized@9.22.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 clsx: 1.2.1 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) react-lifecycles-compat: 3.0.4 - react@19.2.3: {} - - react@19.2.4: {} + react@19.2.7: {} read-pkg@3.0.0: dependencies: @@ -11305,9 +9667,9 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 - readdirp@4.1.2: {} + readdirp@5.0.0: {} readline-sync@1.4.10: {} @@ -11326,18 +9688,18 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -11354,88 +9716,57 @@ snapshots: resolve-from@4.0.0: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: + resolve@2.0.0-next.7: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.0 + object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 reusify@1.1.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rimraf@6.1.3: dependencies: glob: 13.0.6 package-json-from-dist: 1.0.1 - rolldown@1.0.0: + rolldown@1.0.3: dependencies: - '@oxc-project/types': 0.129.0 - '@rolldown/pluginutils': 1.0.0 + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0 - '@rolldown/binding-darwin-arm64': 1.0.0 - '@rolldown/binding-darwin-x64': 1.0.0 - '@rolldown/binding-freebsd-x64': 1.0.0 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 - '@rolldown/binding-linux-arm64-gnu': 1.0.0 - '@rolldown/binding-linux-arm64-musl': 1.0.0 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0 - '@rolldown/binding-linux-s390x-gnu': 1.0.0 - '@rolldown/binding-linux-x64-gnu': 1.0.0 - '@rolldown/binding-linux-x64-musl': 1.0.0 - '@rolldown/binding-openharmony-arm64': 1.0.0 - '@rolldown/binding-wasm32-wasi': 1.0.0 - '@rolldown/binding-win32-arm64-msvc': 1.0.0 - '@rolldown/binding-win32-x64-msvc': 1.0.0 + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 - rollup@4.57.1: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 - fsevents: 2.3.3 - - router@2.2.0: + router@2.2.0(supports-color@5.5.0): dependencies: debug: 4.4.3(supports-color@5.5.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.2 transitivePeerDependencies: - supports-color @@ -11451,9 +9782,9 @@ snapshots: rxjs@8.0.0-alpha.14: {} - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -11478,102 +9809,111 @@ snapshots: safer-buffer@2.1.2: {} - sass-embedded-all-unknown@1.99.0: + sass-embedded-all-unknown@1.100.0: dependencies: - sass: 1.99.0 + sass: 1.100.0 optional: true - sass-embedded-android-arm64@1.99.0: + sass-embedded-android-arm64@1.100.0: optional: true - sass-embedded-android-arm@1.99.0: + sass-embedded-android-arm@1.100.0: optional: true - sass-embedded-android-riscv64@1.99.0: + sass-embedded-android-riscv64@1.100.0: optional: true - sass-embedded-android-x64@1.99.0: + sass-embedded-android-x64@1.100.0: optional: true - sass-embedded-darwin-arm64@1.99.0: + sass-embedded-darwin-arm64@1.100.0: optional: true - sass-embedded-darwin-x64@1.99.0: + sass-embedded-darwin-x64@1.100.0: optional: true - sass-embedded-linux-arm64@1.99.0: + sass-embedded-linux-arm64@1.100.0: optional: true - sass-embedded-linux-arm@1.99.0: + sass-embedded-linux-arm@1.100.0: optional: true - sass-embedded-linux-musl-arm64@1.99.0: + sass-embedded-linux-musl-arm64@1.100.0: optional: true - sass-embedded-linux-musl-arm@1.99.0: + sass-embedded-linux-musl-arm@1.100.0: optional: true - sass-embedded-linux-musl-riscv64@1.99.0: + sass-embedded-linux-musl-riscv64@1.100.0: optional: true - sass-embedded-linux-musl-x64@1.99.0: + sass-embedded-linux-musl-x64@1.100.0: optional: true - sass-embedded-linux-riscv64@1.99.0: + sass-embedded-linux-riscv64@1.100.0: optional: true - sass-embedded-linux-x64@1.99.0: + sass-embedded-linux-x64@1.100.0: optional: true - sass-embedded-unknown-all@1.99.0: + sass-embedded-unknown-all@1.100.0: dependencies: - sass: 1.99.0 + sass: 1.100.0 optional: true - sass-embedded-win32-arm64@1.99.0: + sass-embedded-win32-arm64@1.100.0: optional: true - sass-embedded-win32-x64@1.99.0: + sass-embedded-win32-x64@1.100.0: optional: true - sass-embedded@1.99.0: + sass-embedded@1.100.0: dependencies: - '@bufbuild/protobuf': 2.11.0 + '@bufbuild/protobuf': 2.12.0 colorjs.io: 0.5.2 - immutable: 5.1.5 + immutable: 5.1.6 rxjs: 7.8.2 supports-color: 8.1.1 sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-all-unknown: 1.99.0 - sass-embedded-android-arm: 1.99.0 - sass-embedded-android-arm64: 1.99.0 - sass-embedded-android-riscv64: 1.99.0 - sass-embedded-android-x64: 1.99.0 - sass-embedded-darwin-arm64: 1.99.0 - sass-embedded-darwin-x64: 1.99.0 - sass-embedded-linux-arm: 1.99.0 - sass-embedded-linux-arm64: 1.99.0 - sass-embedded-linux-musl-arm: 1.99.0 - sass-embedded-linux-musl-arm64: 1.99.0 - sass-embedded-linux-musl-riscv64: 1.99.0 - sass-embedded-linux-musl-x64: 1.99.0 - sass-embedded-linux-riscv64: 1.99.0 - sass-embedded-linux-x64: 1.99.0 - sass-embedded-unknown-all: 1.99.0 - sass-embedded-win32-arm64: 1.99.0 - sass-embedded-win32-x64: 1.99.0 + sass-embedded-all-unknown: 1.100.0 + sass-embedded-android-arm: 1.100.0 + sass-embedded-android-arm64: 1.100.0 + sass-embedded-android-riscv64: 1.100.0 + sass-embedded-android-x64: 1.100.0 + sass-embedded-darwin-arm64: 1.100.0 + sass-embedded-darwin-x64: 1.100.0 + sass-embedded-linux-arm: 1.100.0 + sass-embedded-linux-arm64: 1.100.0 + sass-embedded-linux-musl-arm: 1.100.0 + sass-embedded-linux-musl-arm64: 1.100.0 + sass-embedded-linux-musl-riscv64: 1.100.0 + sass-embedded-linux-musl-x64: 1.100.0 + sass-embedded-linux-riscv64: 1.100.0 + sass-embedded-linux-x64: 1.100.0 + sass-embedded-unknown-all: 1.100.0 + sass-embedded-win32-arm64: 1.100.0 + sass-embedded-win32-x64: 1.100.0 - sass@1.99.0: + sass@1.100.0: dependencies: - chokidar: 4.0.3 - immutable: 5.1.5 + chokidar: 5.0.0 + immutable: 5.1.6 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + optional: true + + sass@1.101.0: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.6 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.6 - sax@1.4.4: {} + sax@1.6.0: {} saxes@6.0.0: dependencies: @@ -11585,13 +9925,9 @@ snapshots: semver@6.3.1: {} - semver@7.5.4: - dependencies: - lru-cache: 6.0.0 + semver@7.8.4: {} - semver@7.7.4: {} - - send@1.2.1: + send@1.2.1(supports-color@5.5.0): dependencies: debug: 4.4.3(supports-color@5.5.0) encodeurl: 2.0.0 @@ -11612,7 +9948,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -11636,7 +9972,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 setimmediate@1.0.5: {} @@ -11654,9 +9990,9 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.3: {} + shell-quote@1.8.4: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -11676,11 +10012,11 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.0: + side-channel@1.1.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -11698,13 +10034,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.7.4 - - sirv@2.0.4: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 + semver: 7.8.4 sirv@3.0.2: dependencies: @@ -11712,8 +10042,6 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 - slash@2.0.0: {} - slash@5.1.0: {} slice-ansi@4.0.0: @@ -11734,18 +10062,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 - spdx-license-ids@3.0.22: {} - - sprintf-js@1.0.3: {} + spdx-license-ids@3.0.23: {} stable@0.1.8: {} @@ -11755,8 +10081,6 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} - std-env@4.1.0: {} stop-iteration-iterator@1.1.0: @@ -11764,47 +10088,26 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + storybook@10.4.5(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@storybook/global': 5.0.0 - '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 '@vitest/spy': 3.2.4 '@webcontainer/env': 1.1.1 - esbuild: 0.27.4 + esbuild: 0.27.7 open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.20.0 recast: 0.23.11 - semver: 7.7.4 - use-sync-external-store: 1.6.0(react@19.2.3) - ws: 8.19.0 + semver: 7.8.4 + use-sync-external-store: 1.6.0(react@19.2.7) + ws: 8.21.0 optionalDependencies: - prettier: 3.8.1 - transitivePeerDependencies: - - '@testing-library/dom' - - bufferutil - - react - - react-dom - - utf-8-validate - - storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - '@storybook/global': 5.0.0 - '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@testing-library/jest-dom': 6.9.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/expect': 3.2.4 - '@vitest/spy': 3.2.4 - '@webcontainer/env': 1.1.1 - esbuild: 0.27.4 - open: 10.2.0 - recast: 0.23.11 - semver: 7.7.4 - use-sync-external-store: 1.6.0(react@19.2.4) - ws: 8.19.0 - optionalDependencies: - prettier: 3.8.1 + '@types/react': 19.2.17 + prettier: 3.8.4 transitivePeerDependencies: - '@testing-library/dom' - bufferutil @@ -11816,8 +10119,6 @@ snapshots: dependencies: component-emitter: 2.0.0 - string-argv@0.3.2: {} - string-hash@1.1.3: {} string-width@4.2.3: @@ -11830,71 +10131,76 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 string-width@8.2.1: dependencies: get-east-asian-width: 1.6.0 - strip-ansi: 7.1.2 - - string.prototype.codepointat@0.2.1: {} + strip-ansi: 7.2.0 string.prototype.includes@2.0.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 internal-slot: 1.1.0 regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.1.1 string.prototype.padend@3.1.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 - string.prototype.trim@1.2.10: + string.prototype.trim@1.2.11: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 - string.prototype.trimend@1.0.9: + string.prototype.trimend@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string_decoder@1.1.1: dependencies: @@ -11908,14 +10214,12 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 strip-bom@3.0.0: {} - strip-final-newline@3.0.0: {} - strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -11926,87 +10230,82 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@2.1.1: - dependencies: - js-tokens: 9.0.1 - stubborn-fs@1.2.5: {} - style-dictionary@5.0.0-rc.1(tslib@2.8.1): + style-dictionary@5.4.4(tslib@2.8.1): dependencies: - '@bundled-es-modules/deepmerge': 4.3.1 - '@bundled-es-modules/glob': 10.4.2 + '@bundled-es-modules/deepmerge': 4.3.2 + '@bundled-es-modules/glob': 13.0.6 '@bundled-es-modules/memfs': 4.17.0(tslib@2.8.1) - '@types/node': 22.19.9 '@zip.js/zip.js': 2.8.26(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95) chalk: 5.6.2 change-case: 5.4.4 + colorjs.io: 0.5.2 commander: 12.1.0 is-plain-obj: 4.1.0 json5: 2.2.3 - patch-package: 8.0.1 path-unified: 0.2.0 - prettier: 3.8.1 + prettier: 3.8.4 tinycolor2: 1.6.0 transitivePeerDependencies: - tslib - stylelint-config-recommended-scss@17.0.1(postcss@8.5.14)(stylelint@17.11.0(typescript@6.0.3)): + stylelint-config-recommended-scss@17.0.1(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: - postcss-scss: 4.0.9(postcss@8.5.14) - stylelint: 17.11.0(typescript@6.0.3) - stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@6.0.3)) - stylelint-scss: 7.1.1(stylelint@17.11.0(typescript@6.0.3)) + postcss-scss: 4.0.9(postcss@8.5.15) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) + stylelint-config-recommended: 18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) + stylelint-scss: 7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) optionalDependencies: - postcss: 8.5.14 + postcss: 8.5.15 - stylelint-config-recommended@18.0.0(stylelint@17.11.0(typescript@6.0.3)): + stylelint-config-recommended@18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: - stylelint: 17.11.0(typescript@6.0.3) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) - stylelint-config-standard-scss@17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@6.0.3)): + stylelint-config-standard-scss@17.0.0(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: - stylelint: 17.11.0(typescript@6.0.3) - stylelint-config-recommended-scss: 17.0.1(postcss@8.5.14)(stylelint@17.11.0(typescript@6.0.3)) - stylelint-config-standard: 40.0.0(stylelint@17.11.0(typescript@6.0.3)) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) + stylelint-config-recommended-scss: 17.0.1(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) + stylelint-config-standard: 40.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) optionalDependencies: - postcss: 8.5.14 + postcss: 8.5.15 - stylelint-config-standard@40.0.0(stylelint@17.11.0(typescript@6.0.3)): + stylelint-config-standard@40.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: - stylelint: 17.11.0(typescript@6.0.3) - stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@6.0.3)) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) + stylelint-config-recommended: 18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)) - stylelint-scss@7.1.1(stylelint@17.11.0(typescript@6.0.3)): + stylelint-scss@7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 css-tree: 3.2.1 is-plain-object: 5.0.0 known-css-properties: 0.37.0 postcss-media-query-parser: 0.2.3 postcss-resolve-nested-selector: 0.1.6 - postcss-selector-parser: 7.1.1 + postcss-selector-parser: 7.1.4 postcss-value-parser: 4.2.0 - stylelint: 17.11.0(typescript@6.0.3) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) - stylelint-use-logical-spec@5.0.1(stylelint@17.11.0(typescript@6.0.3)): + stylelint-use-logical-spec@5.0.1(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)): dependencies: - stylelint: 17.11.0(typescript@6.0.3) + stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3) - stylelint@17.11.0(typescript@6.0.3): + stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3): dependencies: '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) - '@csstools/selector-specificity': 6.0.0(postcss-selector-parser@7.1.1) + '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.4) + '@csstools/selector-specificity': 6.0.0(postcss-selector-parser@7.1.4) colord: 2.9.3 - cosmiconfig: 9.0.1(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@6.0.3) css-functions-list: 3.3.3 css-tree: 3.2.1 debug: 4.4.3(supports-color@5.5.0) @@ -12019,15 +10318,14 @@ snapshots: html-tags: 5.1.0 ignore: 7.0.5 import-meta-resolve: 4.2.0 - is-plain-object: 5.0.0 mathml-tag-names: 4.0.0 meow: 14.1.0 micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.14 - postcss-safe-parser: 7.0.1(postcss@8.5.14) - postcss-selector-parser: 7.1.1 + postcss: 8.5.15 + postcss-safe-parser: 7.0.1(postcss@8.5.15) + postcss-selector-parser: 7.1.4 postcss-value-parser: 4.2.0 string-width: 8.2.1 supports-hyperlinks: 4.4.0 @@ -12062,18 +10360,18 @@ snapshots: svg-sprite@2.0.4: dependencies: '@resvg/resvg-js': 2.6.2 - '@xmldom/xmldom': 0.8.11 + '@xmldom/xmldom': 0.8.13 async: 3.2.6 css-selector-parser: 1.4.1 csso: 4.2.0 cssom: 0.5.0 glob: 7.2.3 - js-yaml: 4.1.1 + js-yaml: 4.2.0 lodash.escape: 4.0.1 lodash.merge: 4.6.2 mustache: 4.2.0 prettysize: 2.0.0 - svgo: 2.8.0 + svgo: 2.8.2 vinyl: 2.2.1 winston: 3.19.0 xpath: 0.0.34 @@ -12081,24 +10379,16 @@ snapshots: svg-tags@1.0.0: {} - svgo@2.8.0: + svgo@2.8.2: dependencies: - '@trysound/sax': 0.2.0 commander: 7.2.0 css-select: 4.3.0 css-tree: 1.1.3 csso: 4.2.0 picocolors: 1.1.1 + sax: 1.6.0 stable: 0.1.8 - svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b: - dependencies: - '@trysound/sax': 0.2.0 - css-select: 5.2.2 - css-tree: 3.1.0 - csso: 5.0.5 - lodash: 4.18.1 - symbol-tree@3.2.4: {} sync-child-process@1.0.2: @@ -12109,7 +10399,7 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.13.0 + ajv: 8.20.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -12119,7 +10409,7 @@ snapshots: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.3 + pump: 3.0.4 tar-stream: 2.2.0 tar-stream@2.2.0: @@ -12130,33 +10420,16 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - tdigest@0.1.2: dependencies: bintrees: 1.0.2 - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - text-hex@1.0.0: {} - thingies@2.5.0(tslib@2.8.1): + thingies@2.6.0(tslib@2.8.1): dependencies: tslib: 2.8.1 - tiny-inflate@1.0.3: {} - tiny-invariant@1.3.3: {} tiny-readdir@2.7.4: @@ -12167,35 +10440,24 @@ snapshots: tinycolor2@1.6.0: {} - tinyexec@1.0.2: {} + tinyexec@1.2.4: {} - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinyglobby@0.2.16: + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinypool@0.8.4: {} - tinyrainbow@2.0.0: {} tinyrainbow@3.1.0: {} - tinyspy@2.2.1: {} - tinyspy@4.0.4: {} - tldts-core@7.0.22: {} + tldts-core@7.4.2: {} - tldts@7.0.22: + tldts@7.4.2: dependencies: - tldts-core: 7.0.22 - - tmp@0.2.5: {} + tldts-core: 7.4.2 to-regex-range@5.0.1: dependencies: @@ -12207,13 +10469,9 @@ snapshots: touch@3.1.1: {} - tough-cookie@6.0.0: - dependencies: - tldts: 7.0.22 - tough-cookie@6.0.1: dependencies: - tldts: 7.0.22 + tldts: 7.4.2 tr46@0.0.3: {} @@ -12229,7 +10487,7 @@ snapshots: triple-beam@1.4.1: {} - ts-dedent@2.2.0: {} + ts-dedent@2.3.0: {} tsconfig-paths@3.15.0: dependencies: @@ -12254,11 +10512,9 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-detect@4.1.0: {} - - type-is@2.0.1: + type-is@2.1.0: dependencies: - content-type: 1.0.5 + content-type: 2.0.0 media-typer: 1.1.0 mime-types: 3.0.2 @@ -12270,7 +10526,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -12279,37 +10535,27 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.7: + typed-array-length@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@5.8.2: {} - typescript@6.0.3: {} - ua-is-frozen@0.1.2: {} - ua-parser-js@1.0.41: {} - ua-parser-js@2.0.9: - dependencies: - detect-europe-js: 0.1.2 - is-standalone-pwa: 0.1.1 - ua-is-frozen: 0.1.2 - - ufo@1.6.3: {} + ufo@1.6.4: {} unbox-primitive@1.1.0: dependencies: @@ -12320,33 +10566,39 @@ snapshots: undefsafe@2.0.5: {} - undici-types@6.21.0: {} + undici-types@7.24.6: {} - undici-types@7.16.0: {} - - undici-types@7.21.0: {} - - undici@7.25.0: {} + undici@7.27.2: {} unicorn-magic@0.4.0: {} - universalify@2.0.1: {} - unpipe@1.0.0: {} + unplugin-dts@1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)): + dependencies: + '@rollup/pluginutils': 5.4.0 + '@volar/typescript': 2.4.28 + compare-versions: 6.1.1 + debug: 4.4.3(supports-color@5.5.0) + kolorist: 1.8.0 + local-pkg: 1.2.1 + magic-string: 0.30.21 + typescript: 6.0.3 + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.28.1 + rolldown: 1.0.3 + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) + transitivePeerDependencies: + - supports-color + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 - acorn: 8.16.0 - picomatch: 4.0.3 + acorn: 8.17.0 + picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -12360,15 +10612,11 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.14.1 + qs: 6.15.2 - use-sync-external-store@1.6.0(react@19.2.3): + use-sync-external-store@1.6.0(react@19.2.7): dependencies: - react: 19.2.3 - - use-sync-external-store@1.6.0(react@19.2.4): - dependencies: - react: 19.2.4 + react: 19.2.7 util-deprecate@1.0.2: {} @@ -12382,7 +10630,7 @@ snapshots: is-arguments: 1.2.0 is-generator-function: 1.1.2 is-typed-array: 1.1.15 - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 validate-npm-package-license@3.0.4: dependencies: @@ -12402,134 +10650,62 @@ snapshots: remove-trailing-separator: 1.1.0 replace-ext: 1.0.1 - vite-node@1.6.1(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0): + vite-plugin-dts@5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)): dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@5.5.0) - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.21(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-plugin-dts@4.5.4(@types/node@25.7.0)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): - dependencies: - '@microsoft/api-extractor': 7.56.2(@types/node@25.7.0) - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@volar/typescript': 2.4.28 - '@vue/language-core': 2.2.0(typescript@6.0.3) - compare-versions: 6.1.1 - debug: 4.4.3(supports-color@5.5.0) - kolorist: 1.8.0 - local-pkg: 1.1.2 - magic-string: 0.30.21 - typescript: 6.0.3 + unplugin-dts: 1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) optionalDependencies: - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) transitivePeerDependencies: - - '@types/node' - - rollup + - '@rspack/core' + - '@vue/language-core' + - esbuild + - rolldown - supports-color + - typescript + - webpack - vite@5.4.21(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.14 - rollup: 4.57.1 - optionalDependencies: - '@types/node': 25.2.1 - fsevents: 2.3.3 - lightningcss: 1.32.0 - sass: 1.99.0 - sass-embedded: 1.99.0 - - vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2): + vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.14 - rolldown: 1.0.0 - tinyglobby: 0.2.16 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.7.0 - esbuild: 0.28.0 + '@types/node': 25.9.3 + esbuild: 0.28.1 fsevents: 2.3.3 - sass: 1.99.0 - sass-embedded: 1.99.0 - yaml: 2.8.2 + sass: 1.101.0 + sass-embedded: 1.100.0 - vitest@1.6.1(@types/node@25.2.1)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0): + vitest@4.1.9(@types/node@25.9.3)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)): dependencies: - '@vitest/expect': 1.6.1 - '@vitest/runner': 1.6.1 - '@vitest/snapshot': 1.6.1 - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - acorn-walk: 8.3.4 - chai: 4.5.0 - debug: 4.4.3(supports-color@5.5.0) - execa: 8.0.1 - local-pkg: 0.5.1 - magic-string: 0.30.21 - pathe: 1.1.2 - picocolors: 1.1.1 - std-env: 3.10.0 - strip-literal: 2.1.1 - tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.21(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - vite-node: 1.6.1(@types/node@25.2.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.2.1 - '@vitest/browser': 1.6.1(playwright@1.58.0)(vitest@1.6.1) - '@vitest/ui': 1.6.1(vitest@1.6.1) - jsdom: 27.4.0(canvas@3.2.1) - transitivePeerDependencies: - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vitest@4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): - dependencies: - '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.6 - '@vitest/runner': 4.1.6 - '@vitest/snapshot': 4.1.6 - '@vitest/spy': 4.1.6 - '@vitest/utils': 4.1.6 + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 - obug: 2.1.1 + obug: 2.1.3 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.4 std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.7.0 - '@vitest/browser-playwright': 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) - '@vitest/coverage-v8': 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6) - jsdom: 29.1.1(canvas@3.2.1) + '@types/node': 25.9.3 + '@vitest/browser-playwright': 4.1.9(playwright@1.61.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.9) + '@vitest/coverage-v8': 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) + '@vitest/ui': 4.1.9(vitest@4.1.9) + jsdom: 29.1.1(canvas@3.2.3) transitivePeerDependencies: - msw @@ -12539,9 +10715,9 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - wait-on@9.0.10: + wait-on@9.0.10(supports-color@5.5.0): dependencies: - axios: 1.16.1 + axios: 1.17.0(supports-color@5.5.0) joi: 18.2.1 lodash: 4.18.1 minimist: 1.2.8 @@ -12550,12 +10726,6 @@ snapshots: - debug - supports-color - wasm-pack@0.13.1: - dependencies: - binary-install: 1.1.2 - transitivePeerDependencies: - - debug - watcher@2.3.1: dependencies: dettle: 1.0.5 @@ -12568,18 +10738,11 @@ snapshots: webpack-virtual-modules@0.6.2: {} - whatwg-mimetype@4.0.0: {} - whatwg-mimetype@5.0.0: {} - whatwg-url@15.1.0: - dependencies: - tr46: 6.0.0 - webidl-conversions: 8.0.1 - whatwg-url@16.0.1: dependencies: - '@exodus/bytes': 1.15.0 + '@exodus/bytes': 1.15.1 tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: @@ -12601,7 +10764,7 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -12612,7 +10775,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 which-collection@1.0.2: dependencies: @@ -12621,10 +10784,10 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.20: + which-typed-array@1.1.22: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -12678,7 +10841,13 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -12686,11 +10855,11 @@ snapshots: dependencies: signal-exit: 4.1.0 - ws@8.19.0: {} + ws@8.21.0: {} wsl-utils@0.1.0: dependencies: - is-wsl: 3.1.0 + is-wsl: 3.1.1 xml-name-validator@5.0.0: {} @@ -12700,18 +10869,16 @@ snapshots: xregexp@5.1.2: dependencies: - '@babel/runtime-corejs3': 7.29.0 + '@babel/runtime-corejs3': 7.29.7 y18n@5.0.8: {} yallist@3.1.1: {} - yallist@4.0.0: {} - - yaml@2.8.2: {} - yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@17.7.2: dependencies: cliui: 8.0.1 @@ -12722,17 +10889,25 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@3.2.0: + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yauzl@3.4.0: dependencies: - buffer-crc32: 0.2.13 pend: 1.2.0 yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} - zod-validation-error@4.0.2(zod@4.3.6): + zod-validation-error@4.0.2(zod@4.4.3): dependencies: - zod: 4.3.6 + zod: 4.4.3 - zod@4.3.6: {} + zod@4.4.3: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml index 4e45e6c850..51ad5db958 100644 --- a/frontend/pnpm-workspace.yaml +++ b/frontend/pnpm-workspace.yaml @@ -9,3 +9,25 @@ patchedDependencies: '@zip.js/zip.js@2.8.26': patches/@zip.js__zip.js@2.8.26.patch shamefullyHoist: true + +minimumReleaseAge: 0 + +allowBuilds: + '@parcel/watcher': true + canvas: true + core-js-pure: true + esbuild: true + +overrides: + ajv@>=7.0.0-alpha.0 <8.18.0: ^8.18.0 + immutable@<3.8.3: ^3.8.3 + lodash@<=4.17.23: ^4.17.24 + lodash@>=4.0.0 <=4.17.23: ^4.17.24 + minimatch@>=10.0.0 <10.2.1: ^10.2.1 + minimatch@>=10.0.0 <10.2.3: ^10.2.3 + minimatch@>=9.0.0 <9.0.6: ^9.0.6 + minimatch@>=9.0.0 <9.0.7: ^9.0.7 + postcss@<7.0.36: ^7.0.36 + postcss@<8.4.31: ^8.4.31 + postcss@<8.5.10: ^8.5.10 + yaml@>=2.0.0 <2.8.3: ^2.8.3 diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 4b2dea2b38..9956823eeb 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -9,14 +9,12 @@ {:target :esm :output-dir "resources/public/js/" :asset-path "/js" - ;; :devtools is dev-only, so it lives under :dev -- shadow merges that map - ;; for `watch`/`compile` but not `release`, keeping :devtools-url out of - ;; release entirely (shadow spec-checks it as non-empty-string? whenever the - ;; key is present, even in release). In the devenv SHADOW_SERVER_URL is - ;; always set per workspace (see defaults.env / manage.sh). - :dev {:devtools {:watch-dir "resources/public" - :reload-strategy :full - :devtools-url #shadow/env ["SHADOW_SERVER_URL" :default ""]}} + :devtools {:watch-dir "resources/public" + :reload-strategy :full} + + :dev {;; allows remote-relay per parallel environment + ;; inside :dev so the integration tests won't use it + :devtools {:devtools-url #shadow/env ["SHADOW_SERVER_URL" :default ""]}} :build-options {:manifest-name "manifest.json"} :modules {:shared @@ -92,11 +90,12 @@ {:target :browser :output-dir "resources/public/js/worker/" :asset-path "/js/worker" - ;; Dev-only; see the :main build above for why :devtools lives under :dev. - :dev {:devtools {:devtools-url #shadow/env ["SHADOW_SERVER_URL" :default ""] - :browser-inject :main - :watch-dir "resources/public" - :reload-strategy :full}} + :devtools {:watch-dir "resources/public" + :reload-strategy :full + :browser-inject :main} + :dev {;; allows remote-relay per parallel environment + ;; inside :dev so the integration tests won't use it + :devtools {:devtools-url #shadow/env ["SHADOW_SERVER_URL" :default ""]}} :build-options {:manifest-name "manifest.json"} :modules {:main diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 844fa1197c..ef18c15554 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -92,15 +92,17 @@ (effect [_ state _] (when (and wasm/context-initialized? (not @wasm/context-lost?)) - (let [objects (dsh/lookup-page-objects state)] - (doseq [{:keys [type id parent-id]} redo-changes - :when (contains? wasm-structural-change-types type) - :let [shape-id (case type - :add-obj id - :mov-objects parent-id) - shape (get objects shape-id)] - :when shape] - (wasm.api/process-object shape)) + (let [objects (dsh/lookup-page-objects state) + shapes + (into [] + (keep (fn [{:keys [type id parent-id]}] + (when (contains? wasm-structural-change-types type) + (get objects (case type + :add-obj id + :mov-objects parent-id))))) + redo-changes)] + + (wasm.api/process-objects shapes) (wasm.api/request-render "sync-wasm-structural-changes")))))) (defn- apply-changes-localy diff --git a/frontend/src/app/main/data/event.cljs b/frontend/src/app/main/data/event.cljs index 77e537c685..ef14cc18cf 100644 --- a/frontend/src/app/main/data/event.cljs +++ b/frontend/src/app/main/data/event.cljs @@ -6,7 +6,7 @@ (ns app.main.data.event (:require - ["ua-parser-js" :as ua] + ["@penpot/ua-parser" :as ua] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.json :as json] @@ -66,22 +66,22 @@ (defn- collect-context [] - (let [uagent (new ua/UAParser)] + (let [result (ua/parse)] (merge {:version (:full cf/version) :locale i18n/*current-locale*} - (let [browser (.getBrowser uagent)] + (let [browser (.getBrowser result)] {:browser (obj/get browser "name") :browser-version (obj/get browser "version")}) - (let [engine (.getEngine uagent)] + (let [engine (.getEngine result)] {:engine (obj/get engine "name") :engine-version (obj/get engine "version")}) - (let [os (.getOS uagent) + (let [os (.getOS result) name (obj/get os "name") version (obj/get os "version")] {:os (str name " " version) :os-version version}) - (let [device (.getDevice uagent)] + (let [device (.getDevice result)] (if-let [type (obj/get device "type")] {:device-type type :device-vendor (obj/get device "vendor") @@ -93,7 +93,7 @@ :screen-height (obj/get screen "height") :screen-color-depth (obj/get screen "colorDepth") :screen-orientation (obj/get orientation "type")}) - (let [cpu (.getCPU uagent)] + (let [cpu (.getCPU result)] {:device-arch (obj/get cpu "architecture")})))) (def context diff --git a/frontend/src/app/main/data/exports/assets.cljs b/frontend/src/app/main/data/exports/assets.cljs index 7dadba3d02..6a2105b737 100644 --- a/frontend/src/app/main/data/exports/assets.cljs +++ b/frontend/src/app/main/data/exports/assets.cljs @@ -163,31 +163,46 @@ (when (= status "ended") (dom/trigger-download-uri filename mtype resource-uri))))) +;; TODO: Remove once we support WASM SVG export +(def ^:private wasm-export-types #{:jpeg :webp :png :pdf}) + +(defn- wasm-export-enabled? + "WASM export is available: the flag is set AND render-wasm is active for the + current file. When render-wasm is inactive its shape tree isn't loaded, so a + client-side WASM render would crash." + [state] + (and (contains? cf/flags :wasm-export) + (features/active-feature? state "render-wasm/v1"))) + +(defn- use-wasm-export? + "Whether to take the client-side WASM export path for `export`." + [state export] + (and (wasm-export-enabled? state) + (contains? wasm-export-types (:type export)))) + (defn request-simple-export [{:keys [export]}] - (if (and (contains? cf/flags :wasm-export) - (contains? #{:jpeg :webp :png} (:type export))) - (ptk/reify ::request-simple-export-wasm - ptk/EffectEvent - (effect [_ _ _] - (wasm.exports/export-image export))) + (ptk/reify ::request-simple-export + ptk/UpdateEvent + (update [_ state] + (cond-> state + (not (use-wasm-export? state export)) + (update :export assoc :in-progress true :id uuid/zero))) - (ptk/reify ::request-simple-export - ptk/UpdateEvent - (update [_ state] - (update state :export assoc :in-progress true :id uuid/zero)) - - ptk/WatchEvent - (watch [_ state _] + ptk/WatchEvent + (watch [_ state _] + (if (use-wasm-export? state export) + (do + (case (:type export) + :pdf (wasm.exports/export-pdf export) + (wasm.exports/export-image export)) + (rx/empty)) (let [profile-id (:profile-id state) params {:exports [export] :profile-id profile-id :cmd :export-shapes :wait true - :is-wasm - (and - (features/active-feature? state "render-wasm/v1") - (contains? cf/flags :wasm-export))}] + :is-wasm (wasm-export-enabled? state)}] (rx/concat (rx/of ::dwp/force-persist) @@ -221,10 +236,7 @@ :cmd cmd :profile-id profile-id :force-multiple true - :is-wasm - (and - (features/active-feature? state "render-wasm/v1") - (contains? cf/flags :wasm-export))} + :is-wasm (wasm-export-enabled? state)} (some? name) (assoc :name name)) diff --git a/frontend/src/app/main/data/exports/wasm.cljs b/frontend/src/app/main/data/exports/wasm.cljs index e0feb03132..b91f461795 100644 --- a/frontend/src/app/main/data/exports/wasm.cljs +++ b/frontend/src/app/main/data/exports/wasm.cljs @@ -26,3 +26,18 @@ (dom/trigger-download-uri filename mtype url) (wapi/revoke-uri url) nil)) + +(defn export-pdf-uri + [{:keys [scale object-id]}] + (let [bytes (wasm.api/render-shape-pdf object-id (or scale 1)) + blob (wapi/create-blob bytes "application/pdf")] + (wapi/create-uri blob))) + +(defn export-pdf + [{:keys [suffix name] :as params}] + (let [url (export-pdf-uri params) + filename (str name (or suffix "") ".pdf")] + (dom/trigger-download-uri filename "application/pdf" url) + (js/queueMicrotask #(wapi/revoke-uri url)) + nil)) + diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index 2ed86cf968..77f5d55013 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -9,6 +9,7 @@ [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.files.changes-builder :as pcb] + [app.common.logging :as log] [app.common.time :as ct] [app.main.data.changes :as dch] [app.main.data.event :as ev] @@ -57,45 +58,57 @@ (defn start-plugin! [{:keys [plugin-id name version description host code permissions allow-background]} ^js extensions] - (-> (.ɵloadPlugin - ^js ug/global - #js {:pluginId plugin-id - :name name - :version version - :description description - :host host - :code code - :allowBackground (boolean allow-background) - :permissions (apply array permissions)} - nil - extensions) + (let [load-plugin (unchecked-get ug/global "ɵloadPlugin")] + (if (fn? load-plugin) + (-> (load-plugin + #js {:pluginId plugin-id + :name name + :version version + :description description + :host host + :code code + :allowBackground (boolean allow-background) + :permissions (apply array permissions)} + nil + extensions) - (p/catch (fn [cause] - (ex/print-throwable cause :prefix "Plugin Error") - (errors/flash :cause cause :type :handled))))) + (p/catch (fn [cause] + (ex/print-throwable cause :prefix "Plugin Error") + (errors/flash :cause cause :type :handled)))) + + (log/warn :hint "Plugin runtime not initialized yet" + :plugin-id plugin-id + :action "start-plugin!")))) (defn- load-plugin! [{:keys [plugin-id name version description host code icon permissions]}] (st/emit! (pflag/clear plugin-id) (save-current-plugin plugin-id)) - (-> (.ɵloadPlugin - ^js ug/global - #js {:pluginId plugin-id - :name name - :description description - :version version - :host host - :code code - :icon icon - :permissions (apply array permissions)} - (fn [] - (st/emit! (remove-current-plugin plugin-id)))) + (let [load-plugin (unchecked-get ug/global "ɵloadPlugin")] + (if (fn? load-plugin) + (-> (load-plugin + #js {:pluginId plugin-id + :name name + :description description + :version version + :host host + :code code + :icon icon + :permissions (apply array permissions)} + (fn [] + (st/emit! (remove-current-plugin plugin-id)))) - (p/catch (fn [cause] - (st/emit! (remove-current-plugin plugin-id)) - (ex/print-throwable cause :prefix "Plugin Error") - (errors/flash :cause cause :type :handled))))) + (p/catch (fn [cause] + (st/emit! (remove-current-plugin plugin-id)) + (ex/print-throwable cause :prefix "Plugin Error") + (errors/flash :cause cause :type :handled)))) + + (do + (log/warn :hint "Plugin runtime not initialized yet" + :plugin-id plugin-id + :action "load-plugin!") + (st/emit! (remove-current-plugin plugin-id)))))) (defn open-plugin! [{:keys [url] :as manifest} user-can-edit?] @@ -135,10 +148,15 @@ (defn close-plugin! [{:keys [plugin-id]}] - (try - (.ɵunloadPlugin ^js ug/global plugin-id) - (catch :default e - (.error js/console "Error" e)))) + (let [unload-plugin (unchecked-get ug/global "ɵunloadPlugin")] + (if (fn? unload-plugin) + (try + (unload-plugin plugin-id) + (catch :default e + (.error js/console "Error" e))) + (log/warn :hint "Plugin runtime not initialized yet" + :plugin-id plugin-id + :action "close-plugin!")))) (defn close-current-plugin [& {:keys [close-only-edition-plugins?]}] diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 3b2ca792b8..87b9c1007b 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -17,6 +17,7 @@ [app.common.types.shape-tree :as ctt] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] + [app.config :as cf] [app.main.data.comments :as dcmt] [app.main.data.common :as dcm] [app.main.data.event :as ev] @@ -219,7 +220,8 @@ (ptk/reify ::update-page-position-data ptk/WatchEvent (watch [_ state _] - (if (features/active-feature? state "render-wasm/v1") + (if (and (features/active-feature? state "render-wasm/v1") + (contains? cf/flags :available-viewer-wasm)) (let [objects (dsh/lookup-page-objects state file-id page-id) shapes @@ -581,6 +583,13 @@ (update [_ state] (d/dissoc-in state [:viewer-local :nav-scroll])))) +(defn update-exports-cache + [shapes-key exports] + (ptk/reify ::update-exports-cache + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:inspect-exports-cache shapes-key] exports)))) + (defn complete-animation [] (ptk/reify ::complete-animation diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index cdf9149b97..16115314b0 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -504,8 +504,7 @@ (rx/of (dwu/append-undo entry stack-undo?))) (rx/empty)))))) - (rx/take-until stoper-s)) - (rx/of (mcp/notify-other-tabs-disconnect))))) + (rx/take-until stoper-s))))) ptk/EffectEvent (effect [_ _ _] diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index baea2b8c3d..265ff3139b 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -743,7 +743,8 @@ (update :fills translate-fills) (update :strokes translate-strokes) (d/update-when :content #(txt/transform-nodes process-text-node %)) - (d/update-when :position-data #(mapv process-text-node %))))) + ;; Removes the position-data so it's regenerated + (dissoc :position-data)))) ;; Analyze the rchange and replace staled media and ;; references to the new uploaded media-objects. diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index f399622f54..c145a203b7 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -47,6 +47,20 @@ (let [wglobal (:workspace-global state)] (layout/persist-layout-state! wglobal))))) +(defn toggle-palette + "Toggle the palette tool and change the library it uses" + [selected] + (ptk/reify ::toggle-palette + ptk/WatchEvent + (watch [_ _ _] + (rx/of (layout/toggle-layout-flag :colorpalette) + (mbc/event colorpalette-selected-broadcast-key selected))) + + ptk/EffectEvent + (effect [_ state _] + (let [wglobal (:workspace-global state)] + (layout/persist-layout-state! wglobal))))) + (defn start-picker [] (ptk/reify ::start-picker diff --git a/frontend/src/app/main/data/workspace/mcp.cljs b/frontend/src/app/main/data/workspace/mcp.cljs index 7f0b0e3363..f2c3c069db 100644 --- a/frontend/src/app/main/data/workspace/mcp.cljs +++ b/frontend/src/app/main/data/workspace/mcp.cljs @@ -10,13 +10,10 @@ [app.common.uri :as u] [app.config :as cf] [app.main.broadcast :as mbc] - [app.main.data.event :as ev] - [app.main.data.notifications :as ntf] [app.main.data.plugins :as dp] [app.main.repo :as rp] [app.main.store :as st] - [app.plugins.register :refer [mcp-plugin-id]] - [app.util.i18n :refer [tr]] + [app.plugins.register :as preg] [app.util.timers :as ts] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) @@ -29,7 +26,7 @@ {:code "plugin.js" :name "Penpot MCP Plugin" :version 2 - :plugin-id mcp-plugin-id + :plugin-id preg/mcp-plugin-id :description "This plugin enables interaction with the Penpot MCP server" :allow-background true :permissions @@ -39,6 +36,14 @@ (defonce interval-sub (atom nil)) +(defn connect-mcp + [] + (ptk/reify ::connect-mcp + ptk/WatchEvent + (watch [_ _ _] + (rx/of (mbc/event :mcp/force-disconnect {}) + (ptk/data-event ::connect))))) + (defn finalize-workspace? [event] (= (ptk/type event) :app.main.data.workspace/finalize-workspace)) @@ -72,45 +77,6 @@ (rx/dispose! @interval-sub) (reset! interval-sub nil))) -(declare manage-mcp-notification) - -(defn handle-pong - [{:keys [id data]}] - (ptk/reify ::handle-pong - ptk/UpdateEvent - (update [_ state] - (let [mcp-state (get state :mcp)] - (cond - (= "connected" (:connection-status data)) - (update state :mcp assoc :connected-tab id) - - (and (= "disconnected" (:connection-status data)) - (= id (:connected-tab mcp-state))) - (update state :mcp dissoc :connected-tab) - - :else - state))) - - ptk/WatchEvent - (watch [_ _ _] - (rx/of (manage-mcp-notification))))) - -;; This event will arrive when a new workspace is open in another tab -(defn handle-ping - [] - (ptk/reify ::handle-ping - ptk/WatchEvent - (watch [_ state _] - (let [conn-status (get-in state [:mcp :connection-status])] - (rx/of (mbc/event :mcp/pong {:connection-status conn-status})))))) - -(defn notify-other-tabs-disconnect - [] - (ptk/reify ::notify-other-tabs-disconnect - ptk/WatchEvent - (watch [_ _ _] - (rx/of (mbc/event :mcp/pong {:connection-status "disconnected"}))))) - ;; This event will arrive when the mcp is enabled in the dashboard (defn update-mcp-status [value] @@ -121,12 +87,10 @@ ptk/WatchEvent (watch [_ _ _] - (rx/merge - (rx/of (manage-mcp-notification)) - (case value - true (rx/of (ptk/data-event ::connect)) - false (rx/of (ptk/data-event ::disconnect)) - nil))))) + (case value + true (rx/of (connect-mcp)) + false (rx/of (ptk/data-event ::disconnect)) + nil)))) (defn update-mcp-connection-status [value] @@ -137,20 +101,13 @@ ptk/WatchEvent (watch [_ _ _] - (rx/of (manage-mcp-notification) - (mbc/event :mcp/pong {:connection-status value}))))) - -(defn connect-mcp - [] - (ptk/reify ::connect-mcp - ptk/UpdateEvent - (update [_ state] - (update state :mcp assoc :connected-tab (:session-id state))) - - ptk/WatchEvent - (watch [_ _ _] - (rx/of (mbc/event :mcp/force-disconect {}) - (ptk/data-event ::connect))))) + ;; Only one MCP plugin instance may be active across browser tabs. + ;; When this tab becomes connected, tell every other tab to + ;; disconnect (which also stops their reconnect watcher). Otherwise + ;; several tabs stay connected at once and the MCP server reports + ;; "multiple instances connected" and the agent fails. + (when (= "connected" value) + (rx/of (mbc/event :mcp/force-disconnect {})))))) ;; This event will arrive when the user selects disconnect on the menu ;; or there is a broadcast message for disconnection @@ -166,77 +123,54 @@ (effect [_ _ _] (stop-reconnect-watcher!)))) -(defn- manage-mcp-notification - [] - (ptk/reify ::manage-mcp-notification - ptk/WatchEvent - (watch [_ state _] - (let [mcp-state (get state :mcp) - - mcp-enabled? (-> state :profile :props :mcp-enabled) - - current-tab-id (get state :session-id) - connected-tab-id (get mcp-state :connected-tab)] - - (if mcp-enabled? - (if (= connected-tab-id current-tab-id) - (rx/of (ntf/hide)) - (rx/of (ntf/dialog - {:content (tr "notifications.mcp.active-in-another-tab") - :cancel {:label (tr "labels.dismiss") - :callback #(st/emit! (ntf/hide) - (ev/event {::ev/name "dismiss-mcp-tab-switch" - ::ev/origin "workspace-notification"}))} - :accept {:label (tr "labels.switch") - :callback #(st/emit! (connect-mcp) - (ev/event {::ev/name "confirm-mcp-tab-switch" - ::ev/origin "workspace-notification"}))}}))) - (rx/of (ntf/hide))))))) - (defn init-mcp [stream] - (->> (rp/cmd! :get-current-mcp-token) - (rx/tap - (fn [{:keys [token]}] - (when token - (dp/start-plugin! - (assoc default-manifest - :url (str (u/join cf/public-uri "plugins/mcp/manifest.json")) - :host (str (u/join cf/public-uri "plugins/mcp/"))) + ;; Wait for plugins runtime to be initialized before starting the MCP plugin. + ;; This ensures global.ɵloadPlugin is available when start-plugin! is called. + (->> (rx/from (preg/wait-for-runtime)) + (rx/mapcat + (fn [_] + (->> (rp/cmd! :get-current-mcp-token) + (rx/tap + (fn [{:keys [token]}] + (when token + (dp/start-plugin! + (assoc default-manifest + :url (str (u/join cf/public-uri "plugins/mcp/manifest.json")) + :host (str (u/join cf/public-uri "plugins/mcp/"))) - ;; API extension for MCP server - #js {:mcp - #js - {:getToken (constantly token) - :getServerUrl #(str cf/mcp-ws-uri) - :setMcpStatus - (fn [status] - (when (= status "connected") - (start-reconnect-watcher!)) - (st/emit! (update-mcp-connection-status status)) - (log/info :hint "MCP STATUS" :status status)) + ;; API extension for MCP server + #js {:mcp + #js + {:getToken (constantly token) + :getServerUrl #(str cf/mcp-ws-uri) + :setMcpStatus + (fn [status] + (when (= status "connected") + (start-reconnect-watcher!)) + (st/emit! (update-mcp-connection-status status)) + (log/info :hint "MCP STATUS" :status status)) - :on - (fn [event cb] - (when-let [event - (case event - "disconnect" ::disconnect - "connect" ::connect - nil)] + :on + (fn [event cb] + (when-let [event + (case event + "disconnect" ::disconnect + "connect" ::connect + nil)] - (let [stopper (rx/filter finalize-workspace? stream)] - (->> stream - (rx/filter (ptk/type? event)) - (rx/take-until stopper) - (rx/subs! #(cb))))))}})))) - (rx/ignore))) + (let [stopper (rx/filter finalize-workspace? stream)] + (->> stream + (rx/filter (ptk/type? event)) + (rx/take-until stopper) + (rx/subs! #(cb))))))}}))))))))) (defn init [] (ptk/reify ::init ptk/UpdateEvent (update [_ state] - (update state :mcp assoc :connected-tab (:session-id state) :active true)) + (update state :mcp assoc :active true)) ptk/WatchEvent (watch [_ state stream] @@ -251,22 +185,8 @@ (rx/merge (init-mcp stream) - (rx/of (mbc/event :mcp/ping {})) - (->> mbc/stream - (rx/filter (mbc/type? :mcp/ping)) - (rx/filter (fn [{:keys [id]}] - (not= session-id id))) - (rx/map handle-ping)) - - (->> mbc/stream - (rx/filter (mbc/type? :mcp/pong)) - (rx/filter (fn [{:keys [id]}] - (not= session-id id))) - (rx/map handle-pong)) - - (->> mbc/stream - (rx/filter (mbc/type? :mcp/force-disconect)) + (rx/filter (mbc/type? :mcp/force-disconnect)) (rx/filter (fn [{:keys [id]}] (not= session-id id))) (rx/map deref) @@ -276,9 +196,9 @@ (->> mbc/stream (rx/filter (mbc/type? :mcp/enable)) (rx/mapcat (fn [_] - ;; NOTE: we don't need an explicit - ;; connect because the plugin has - ;; auto-connect + ;; Re-init so the force-disconnect + ;; listener is set up now that MCP + ;; is enabled. (rx/of (update-mcp-status true) (init))))) @@ -286,7 +206,6 @@ (rx/filter (mbc/type? :mcp/disable)) (rx/mapcat (fn [_] (rx/of (update-mcp-status false) - (init) (user-disconnect-mcp)))))) (rx/take-until stoper-s)))))) diff --git a/frontend/src/app/main/data/workspace/pages.cljs b/frontend/src/app/main/data/workspace/pages.cljs index 9ef4a20f19..37d12730b8 100644 --- a/frontend/src/app/main/data/workspace/pages.cljs +++ b/frontend/src/app/main/data/workspace/pages.cljs @@ -88,6 +88,7 @@ (let [page (dsh/lookup-page state file-id page-id) uris (into #{} xf:collect-file-media (:objects page))] (rx/merge + (rx/of (ptk/data-event ::initialized page-id)) (->> (rx/from uris) (rx/map #(http/fetch-data-uri % false)) (rx/ignore)) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 6d6f1b691b..30b2ecf40b 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -957,10 +957,10 @@ txt/text-transform-attrs))) values (cond-> values (number? (:line-height values)) - (update :line-height str) + (update :line-height #(str (mth/precision % 2))) (number? (:letter-spacing values)) - (update :letter-spacing str)) + (update :letter-spacing #(str (mth/precision % 2)))) typ-id (uuid/next) typ (-> (if multiple? diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 80c83f6153..3770a24a4a 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -85,40 +85,70 @@ (let [request (create-request file-id page-id shape-id tag)] (q/enqueue-unique queue request (partial render-thumbnail state file-id page-id shape-id tag)))) +(defn- clear-thumbnail-batch + [] + (let [pending (volatile! nil)] + (ptk/reify ::clear-thumbnail-batch + ptk/UpdateEvent + (update [_ state] + (let [items (get state ::thumbnails-deletion-queue)] + (when (seq items) + (vreset! pending items)) + (dissoc state ::thumbnails-deletion-queue))) + + ptk/WatchEvent + (watch [_ _ _] + (let [items (reduce-kv (fn [acc object-id uri] + (when (str/starts-with? uri "blob:") + (tm/schedule-on-idle (partial wapi/revoke-uri uri))) + (conj acc object-id)) + [] + @pending)] + (l/dbg :hint "clear-thumbnail-batch" :total (count items)) + (->> (rx/from (partition-all 200 items)) + (rx/mapcat + (fn [batch] + (l/dbg :hint "clear-thumbnail-batch" :batch-size (count batch)) + (->> (rp/cmd! :delete-file-object-thumbnails + {:object-ids (vec batch)}) + (rx/catch rx/empty) + (rx/ignore)))))))))) + +(defn remove-from-deletion-queue + "Removes an object-id from the pending deletion queue in state. + Used by update-thumbnail to cancel a pending batched delete before + creating a new thumbnail for the same object." + [object-id] + (ptk/reify ::remove-from-deletion-queue + ptk/UpdateEvent + (update [_ state] + (update state ::thumbnails-deletion-queue dissoc object-id)))) + (defn clear-thumbnail ([file-id page-id frame-id tag] (clear-thumbnail file-id (thc/fmt-object-id file-id page-id frame-id tag))) - ([file-id object-id] - (let [pending (volatile! false)] - (ptk/reify ::clear-thumbnail - cljs.core/IDeref - (-deref [_] object-id) + ([_file-id object-id] + (ptk/reify ::clear-thumbnail + cljs.core/IDeref + (-deref [_] object-id) - ptk/UpdateEvent - (update [_ state] + ptk/UpdateEvent + (update [_ state] + (let [uri (dm/get-in state [:thumbnails object-id])] + (l/dbg :hint "clear-thumbnail" :object-id object-id :uri uri) (-> state - (update :thumbnails - (fn [thumbs] - (if-let [uri (get thumbs object-id)] - (do (vreset! pending uri) - (dissoc thumbs object-id)) - thumbs))) - (update :thumbnails-meta dissoc object-id))) + (update ::thumbnails-deletion-queue assoc object-id uri) + (update :thumbnails dissoc object-id) + (update :thumbnails-meta dissoc object-id)))) - ptk/WatchEvent - (watch [_ _ _] - (if-let [uri @pending] - (do - (l/trc :hint "clear-thumbnail" :uri uri) - (when (str/starts-with? uri "blob:") - (tm/schedule-on-idle (partial wapi/revoke-uri uri))) - - (let [params {:file-id file-id - :object-id object-id}] - (->> (rp/cmd! :delete-file-object-thumbnail params) - (rx/catch rx/empty) - (rx/ignore)))) - (rx/empty))))))) + ptk/WatchEvent + (watch [_ _ stream] + (let [stopper-s (->> stream + (rx/filter (ptk/type? ::clear-thumbnail)))] + (->> (rx/timer 200) + (rx/take 1) + (rx/map (fn [_] (clear-thumbnail-batch))) + (rx/take-until stopper-s))))))) (defn assoc-thumbnail [object-id uri] @@ -173,7 +203,8 @@ :tag (or tag "frame")}] (rx/merge - (rx/of (assoc-thumbnail object-id uri)) + (rx/of (assoc-thumbnail object-id uri) + (remove-from-deletion-queue object-id)) (->> (rp/cmd! :create-file-object-thumbnail params) (rx/catch rx/empty) (rx/ignore)))))) @@ -305,6 +336,14 @@ ;; and interrupt any ongoing update-thumbnail process ;; related to current frame-id (->> all-commits-s + ;; Ensure each clear-thumbnail event is dispatched in its + ;; own macrotask tick. Without this, multiple changes + ;; arriving on the same synchronous tick would emit + ;; several clear-thumbnail events back-to-back, causing + ;; their debounce timers (rx/take-until stopper-s) to + ;; race and potentially leave multiple clear-thumbnail-batch + ;; timers alive simultaneously. + (rx/observe-on :async) (rx/mapcat (fn [[tag frame-id]] (rx/of (clear-thumbnail file-id page-id frame-id tag))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 791520efd9..771bbcd04f 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cph] + [app.common.time :as ct] [app.common.types.shape-tree :as ctt] [app.common.types.shape.layout :as ctl] [app.common.types.tokens-lib :as ctob] @@ -155,6 +156,14 @@ (def mcp (l/derived :mcp st/state)) +(def mcp-key-expired? + (l/derived (fn [state] + (when-let [expires-at (some->> (:access-tokens state) + (some #(when (= (:type %) "mcp") %)) + :expires-at)] + (> (ct/now) expires-at))) + st/state)) + (def workspace-drawing (l/derived :workspace-drawing st/state)) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 7c6ee76e1e..5bd1a63f6d 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -30,6 +30,7 @@ [app.common.types.shape.layout :as ctl] [app.config :as cfg] [app.main.fonts :as fonts] + [app.main.render-viewer-wasm :as rwv] [app.main.ui.context :as muc] [app.main.ui.shapes.bool :as bool] [app.main.ui.shapes.circle :as circle] @@ -524,32 +525,6 @@ (for [shape shapes] [:& shape-wrapper {:key (dm/str (:id shape)) :shape shape}])]]])) -(defn render-to-canvas - [objects canvas bounds scale object-id on-render] - (let [width (.-width canvas) - height (.-height canvas) - os-canvas (js/OffscreenCanvas. width height)] - (try - (when (wasm.api/init-canvas-context os-canvas) - (wasm.api/initialize-viewport - objects scale bounds - :background-opacity 0 - :on-render - (fn [] - (wasm.api/render-sync-shape object-id) - (ts/raf - (fn [] - (let [bitmap (.transferToImageBitmap os-canvas) - ctx2d (.getContext canvas "2d")] - (.clearRect ctx2d 0 0 width height) - (.drawImage ctx2d bitmap 0 0) - (dom/set-attribute! canvas "id" (dm/str "screenshot-" object-id)) - (wasm.api/clear-canvas) - (on-render))))))) - (catch :default e - (js/console.error "Error initializing canvas context:" e) - false)))) - (mf/defc object-wasm {::mf/wrap [mf/memo]} [{:keys [objects object-id skip-children scale on-render] :as props}] @@ -574,7 +549,7 @@ (p/fmap (fn [ready?] (when ready? - (render-to-canvas objects canvas bounds scale object-id on-render)))))))) + (rwv/render-to-canvas objects canvas bounds scale object-id on-render)))))))) [:canvas {:ref canvas-ref :width (* scale width) diff --git a/frontend/src/app/main/render_viewer_wasm.cljs b/frontend/src/app/main/render_viewer_wasm.cljs new file mode 100644 index 0000000000..9ae5d63994 --- /dev/null +++ b/frontend/src/app/main/render_viewer_wasm.cljs @@ -0,0 +1,269 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns app.main.render-viewer-wasm + "WASM offscreen rendering for the shared viewer (snapshot + fixed-scroll)." + (:require + [app.common.data.macros :as dm] + [app.render-wasm.api :as wasm.api] + [app.render-wasm.wasm :as wasm] + [app.util.dom :as dom] + [app.util.timers :as ts] + [app.util.webapi :as webapi] + [goog.events :as events] + [promesa.core :as p] + [rumext.v2 :as mf])) + +;; The WASM module is a single global instance; serialize offscreen work. +(defonce ^:private wasm-render-queue (atom (p/resolved nil))) + +(defn- enqueue-wasm-render! + [task] + (let [next-p (-> @wasm-render-queue + (p/handle (fn [_ _] (task))))] + (reset! wasm-render-queue (p/handle next-p (fn [_ _] nil))) + next-p)) + +(defonce ^:private viewer-snapshot + (atom {:os-canvas nil + :page-key nil + :canvas-w 0 + :canvas-h 0 + :dpr 1})) + +(defn- reset-viewer-snapshot! [] + (reset! viewer-snapshot + {:os-canvas nil + :page-key nil + :canvas-w 0 + :canvas-h 0 + :dpr 1})) + +(defn- draw-bitmap! + [canvas os-canvas object-id vis-w vis-h finish] + (ts/raf + (fn [] + (let [ctx2d (.getContext canvas "2d")] + (.clearRect ctx2d 0 0 vis-w vis-h) + ;; Draw directly from OffscreenCanvas so it can be reused across passes. + (.drawImage ctx2d os-canvas 0 0 vis-w vis-h) + (dom/set-attribute! canvas "id" (str "screenshot-" object-id)) + (finish))))) + +(defn- viewer-disable-wasm-ui-overlay! + "Workspace WASM UI (rulers + rounded viewport frame) is composited in + `present_frame`; the viewer must not show that chrome." + [] + (wasm.api/set-rulers-frame-visible! false) + (wasm.api/set-rulers-visible! false)) + +(defn- viewer-apply-layer-mask! + [include-ids clear-fills-ids] + (wasm.api/clear-render-include-filter!) + (when (seq include-ids) + (wasm.api/set-render-include-filter! include-ids)) + (doseq [id clear-fills-ids] + (wasm.api/use-shape id) + (wasm.api/clear-shape-fills!))) + +(defn- viewer-restore-layer-mask! + [page-objects clear-fills-ids] + (wasm.api/clear-render-include-filter!) + (doseq [id clear-fills-ids] + (wasm.api/use-shape id) + (wasm.api/set-shape-fills id (get-in page-objects [id :fills] []) false))) + +(defn- viewer-do-render! + [page-objects canvas os-canvas object-id vis-w vis-h scale size + include-ids clear-fills-ids finish] + (viewer-disable-wasm-ui-overlay!) + (viewer-apply-layer-mask! include-ids clear-fills-ids) + (wasm.api/set-viewer-viewport! scale size) + (wasm.api/render-sync-shape object-id) + (viewer-restore-layer-mask! page-objects clear-fills-ids) + (draw-bitmap! canvas os-canvas object-id vis-w vis-h finish)) + +(defn- render-to-canvas* + [objects canvas bounds scale object-id on-render] + (p/create + (fn [resolve _reject] + (let [width (.-width canvas) + height (.-height canvas) + prev-disable @wasm/disable-request-render? + finish (fn [] + (reset! wasm/disable-request-render? prev-disable) + (when (fn? on-render) (on-render)) + (resolve nil))] + (try + (reset! wasm/disable-request-render? true) + (let [os-canvas (js/OffscreenCanvas. width height)] + (if (wasm.api/init-canvas-context os-canvas) + (wasm.api/initialize-viewport + objects scale bounds + :background-opacity 0 + :force-sync true + :on-render + (fn [] + (viewer-disable-wasm-ui-overlay!) + (wasm.api/render-sync-shape object-id) + (draw-bitmap! canvas os-canvas object-id width height + (fn [] + (wasm.api/clear-canvas {:lose-browser-context? false}) + (reset-viewer-snapshot!) + (finish))))) + (finish))) + (catch :default e + (js/console.error "Error initializing canvas context:" e) + (finish))))))) + +(defn render-to-canvas + "One-shot WASM render into `canvas` (exports, thumbnails). Serialized globally." + [objects canvas bounds scale object-id on-render] + (enqueue-wasm-render! + (fn [] + (render-to-canvas* objects canvas bounds scale object-id on-render)))) + +(defn- render-viewer-frame* + [page-key page-objects canvas size scale object-id on-render + {:keys [include-ids clear-fills-ids] :or {clear-fills-ids #{}}}] + (p/create + (fn [resolve _reject] + (let [prev-disable @wasm/disable-request-render? + finish (fn [] + (reset! wasm/disable-request-render? prev-disable) + (when (fn? on-render) (on-render)) + (resolve nil)) + vis-w (.-width canvas) + vis-h (.-height canvas) + dpr (wasm.api/get-dpr) + snap @viewer-snapshot + same-page? (and (some? page-key) (identical? page-key (:page-key snap))) + same-size? (and (= vis-w (:canvas-w snap)) + (= vis-h (:canvas-h snap)) + (= dpr (:dpr snap))) + os (:os-canvas snap) + do-render! (fn [os-canvas] + (viewer-do-render! page-objects canvas os-canvas object-id + vis-w vis-h scale size include-ids + clear-fills-ids finish))] + + (reset! wasm/disable-request-render? true) + + (try + (if (and same-page? (wasm.api/initialized?) os) + (do + (when-not same-size? + (wasm.api/resize-offscreen-canvas! os vis-w vis-h) + (swap! viewer-snapshot assoc :canvas-w vis-w :canvas-h vis-h :dpr dpr)) + (do-render! os)) + (let [os-canvas (js/OffscreenCanvas. vis-w vis-h)] + (when (wasm.api/initialized?) + (wasm.api/clear-canvas {:lose-browser-context? false})) + (if (wasm.api/init-canvas-context os-canvas) + (do + (reset! viewer-snapshot + {:os-canvas os-canvas + :page-key page-key + :canvas-w vis-w + :canvas-h vis-h + :dpr dpr}) + (wasm.api/initialize-viewport + page-objects scale size + :background-opacity 0 + :force-sync true + :on-render #(do-render! os-canvas))) + (finish)))) + (catch :default e + (js/console.error "viewer-snapshot: render error" e) + (finish))))))) + +(defn- use-fixed-scroll-sync! + [enabled? layer-ref] + (mf/use-layout-effect + (mf/deps enabled?) + (fn [] + (when enabled? + (let [section (dom/get-element "viewer-section") + sync! + (fn [] + (when-let [layer (mf/ref-val layer-ref)] + (dom/set-style! layer "transform" + (dm/str "translate(" + (or (dom/get-h-scroll-pos section) 0) "px, " + (or (dom/get-scroll-pos section) 0) "px)"))))] + (when section + (sync!) + (let [key (events/listen section "scroll" (fn [_] (sync!)))] + #(events/unlistenByKey key)))))))) + +(defn- use-viewer-dpr-key + "Bump a counter when browser zoom changes devicePixelRatio so WASM canvases + are resized like the workspace viewport." + [] + (let [dpr-key (mf/use-state 0)] + (mf/use-effect + (mf/deps []) + (fn [] + (webapi/on-dpr-change (fn [_] (swap! dpr-key inc))))) + @dpr-key)) + +(defn- use-viewer-wasm-layers! + [page-id page-objects size scale frame-id not-fixed-ref fixed-ref + not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids delta dpr-key] + ;; The hot-areas SVG shifts every object by `-(size + delta)` so the frame + ;; `selrect` lands flush against the overlay snap side, ignoring the extra + ;; padding reserved for shadows/blur/strokes. Bake the same `delta` into the + ;; WASM view origin so the rendered canvas aligns with that SVG (otherwise it + ;; appears offset by the shadow margin). + (let [render-size (-> size + (update :x + (:x delta 0)) + (update :y + (:y delta 0)))] + (mf/use-layout-effect + (mf/deps page-id page-objects render-size scale frame-id dpr-key + not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids) + (fn [] + (when (get page-objects frame-id) + (->> @wasm.api/module + (p/fmap + (fn [ready?] + (when ready? + (let [not-fixed-canvas (mf/ref-val not-fixed-ref) + fixed-canvas (mf/ref-val fixed-ref) + passes + (cond-> [] + not-fixed-canvas + (conj {:canvas not-fixed-canvas + :opts (cond-> {} + (seq not-fixed-include-ids) + (assoc :include-ids not-fixed-include-ids))}) + + (and fixed-canvas (seq fixed-include-ids)) + (conj {:canvas fixed-canvas + :opts (cond-> {:include-ids fixed-include-ids} + (seq fixed-clear-fills-ids) + (assoc :clear-fills-ids fixed-clear-fills-ids))}))] + (when (seq passes) + (enqueue-wasm-render! + (fn [] + (reduce (fn [chain {:keys [canvas opts]}] + (p/then chain + #(render-viewer-frame* page-id page-objects + canvas render-size scale frame-id + nil opts))) + (p/resolved nil) + passes)))))))))))))) + +(defn use-viewer-wasm-viewport! + "WASM render passes and fixed-scroll DOM sync for the viewer viewport." + [page-id page-objects size scale frame-id + not-fixed-ref fixed-ref fixed-scroll-layer-ref + not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids delta] + (use-fixed-scroll-sync! (some? fixed-scroll-layer-ref) fixed-scroll-layer-ref) + (let [dpr-key (use-viewer-dpr-key)] + (use-viewer-wasm-layers! page-id page-objects size scale frame-id + not-fixed-ref fixed-ref + not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids + delta dpr-key))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 5d95652b1d..157ae24d9b 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -27,7 +27,7 @@ [app.main.ui.nitrate.entry :as nitrate-entry] [app.main.ui.notifications :as notifications] [app.main.ui.onboarding.questions :refer [questions-modal]] - [app.main.ui.onboarding.team-choice :refer [onboarding-team-modal]] + [app.main.ui.onboarding.team-choice :refer [onboarding-team-modal*]] [app.main.ui.releases :refer [release-notes-modal]] [app.main.ui.static :as static] [app.util.dom :as dom] @@ -238,14 +238,14 @@ #_[:& app.main.ui.releases/release-notes-modal {:version "2.5"}] #_[:& app.main.ui.onboarding/onboarding-templates-modal] #_[:& app.main.ui.onboarding/onboarding-modal] - #_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal] + #_[:> app.main.ui.onboarding.team-choice/onboarding-team-modal*] (cond show-question-modal? [:& questions-modal] show-team-modal? - [:& onboarding-team-modal {:go-to-team true}] + [:> onboarding-team-modal* {:go-to-team true}] show-release-modal? [:& release-notes-modal {:version (:main cf/version)}]) @@ -272,7 +272,7 @@ [:& questions-modal] show-team-modal? - [:& onboarding-team-modal {:go-to-team false}] + [:> onboarding-team-modal* {:go-to-team false}] show-release-modal? [:& release-notes-modal {:version (:main cf/version)}])) diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index ad0ac7e73d..5f821fab9e 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -11,7 +11,7 @@ [app.main.data.auth :as da] [app.main.store :as st] [app.main.ui.auth.login :refer [login-page]] - [app.main.ui.auth.recovery :refer [recovery-page]] + [app.main.ui.auth.recovery :refer [recovery-page*]] [app.main.ui.auth.recovery-request :refer [recovery-request-page]] [app.main.ui.auth.register :refer [register-page* register-success-page* register-validate-page* terms-register*]] [app.main.ui.icons :as deprecated-icon] @@ -69,7 +69,7 @@ [:& recovery-request-page] :auth-recovery - [:& recovery-page {:params params}]) + [:> recovery-page* {:params params}]) (when (= section :auth-register) [:> terms-register*])]])) diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs index ac648d31db..5ee49525db 100644 --- a/frontend/src/app/main/ui/auth/recovery.cljs +++ b/frontend/src/app/main/ui/auth/recovery.cljs @@ -44,8 +44,8 @@ :password (get-in @form [:clean-data :password-2])}] (st/emit! (du/recover-profile (with-meta params mdata))))) -(mf/defc recovery-form - [{:keys [params] :as props}] +(mf/defc recovery-form* + [{:keys [params]}] (let [form (fm/use-form :schema schema:recovery-form :initial params)] @@ -73,13 +73,13 @@ ;; --- Recovery Request Page -(mf/defc recovery-page - [{:keys [params] :as props}] +(mf/defc recovery-page* + [{:keys [params]}] [:div {:class (stl/css :auth-form-wrapper)} [:h1 {:class (stl/css :auth-title)} "Forgot your password?"] [:div {:class (stl/css :auth-subtitle)} "Please enter your new password"] [:hr {:class (stl/css :separator)}] - [:& recovery-form {:params params}] + [:> recovery-form* {:params params}] [:div {:class (stl/css :links)} [:div {:class (stl/css :go-back)} diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 099b5e43dd..0d4a5fc55f 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -557,6 +557,32 @@ (dom/stop-propagation event) (swap! items (fn [items] (if (c/empty? items) items (pop items))))))))) + on-paste + (mf/use-fn + (fn [event] + (let [paste-data (-> event .-clipboardData (.getData "text"))] + (when (and (string? paste-data) + (re-find #"[,\s]" paste-data)) + (dom/prevent-default event) + (dom/stop-propagation event) + + ;; Mark as touched + (swap! form assoc-in [:touched input-name] true) + + ;; Split pasted text by commas and/or whitespace, add each valid part + (let [parts (->> (str/split paste-data #",|\s+") + (map str/trim) + (remove str/empty?))] + (doseq [part parts] + (when (valid-item-fn part) + (swap! items conj-dedup {:text part + :valid true + :caution (caution-item-fn part)}))) + + ;; Reset input value and mark as untouched after successful paste + (reset! value "") + (swap! form assoc-in [:touched input-name] false)))))) + on-blur (mf/use-fn (fn [_] @@ -590,6 +616,7 @@ :on-focus on-focus :on-blur on-blur :on-key-down on-key-down + :on-paste on-paste :value @value :on-change on-change :placeholder (when empty? label)}] diff --git a/frontend/src/app/main/ui/dashboard/deleted.cljs b/frontend/src/app/main/ui/dashboard/deleted.cljs index 33c9ea5d74..e5be919ea3 100644 --- a/frontend/src/app/main/ui/dashboard/deleted.cljs +++ b/frontend/src/app/main/ui/dashboard/deleted.cljs @@ -12,10 +12,12 @@ [app.main.data.common :as dcm] [app.main.data.dashboard :as dd] [app.main.data.modal :as modal] + [app.main.data.nitrate :as dnt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.dashboard.grid :refer [grid*]] + [app.main.ui.dashboard.subscription :refer [get-subscription-type]] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]] [app.main.ui.hooks :as hooks] @@ -239,13 +241,15 @@ ;; Calculate deletion days based on team subscription deletion-days - (let [subscription (get team :subscription) - sub-type (get subscription :type) - sub-status (get subscription :status) - canceled? (contains? #{"canceled" "unpaid"} sub-status)] + (let [profile (mf/deref refs/profile) + subscription-type (get-subscription-type (:subscription team)) + nitrate-type (get-in profile [:subscription :type]) + nitrate-active? (dnt/is-valid-license? profile)] (cond - (and (= "unlimited" sub-type) (not canceled?)) 30 - (and (= "enterprise" sub-type) (not canceled?)) 90 + (and nitrate-active? + (contains? #{"enterprise" "nitrate"} nitrate-type)) 90 + (= subscription-type "unlimited") 30 + (= subscription-type "enterprise") 90 :else 7)) on-delete-all diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 8892687490..6c248beb04 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -273,9 +273,8 @@ :on-click on-delete} deprecated-icon/close]]]))]])) -(mf/defc installed-font-context-menu - {::mf/props :obj - ::mf/private true} +(mf/defc installed-font-context-menu* + {::mf/private true} [{:keys [is-open on-close on-edit on-download on-delete]}] (let [options (mf/with-memo [on-edit on-download on-delete] [{:name (tr "labels.edit") @@ -296,10 +295,9 @@ :left -115 :options options}])) -(mf/defc installed-font - {::mf/props :obj - ::mf/private true - ::mf/memo true} +(mf/defc installed-font* + {::mf/private true + ::mf/wrap [mf/memo]} [{:keys [font-id variants can-edit]}] (let [font (first variants) @@ -445,7 +443,7 @@ :on-click on-menu-open} deprecated-icon/menu] - [:& installed-font-context-menu + [:> installed-font-context-menu* {:on-close on-menu-close :is-open menu-open? :on-delete on-delete-font @@ -480,10 +478,10 @@ (for [[font-id variants] (->> (vals fonts) (filter matches?) (group-by :font-id))] - [:& installed-font {:key (dm/str font-id "-installed") - :font-id font-id - :can-edit can-edit - :variants variants}])] + [:> installed-font* {:key (dm/str font-id "-installed") + :font-id font-id + :can-edit can-edit + :variants variants}])] (nil? fonts) [:div {:class (stl/css :fonts-placeholder)} diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 0a4698eab3..a279c62420 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -50,10 +50,8 @@ :entries entries :on-finish-import on-finish-import}))))))) -(mf/defc import-form - {::mf/forward-ref true - ::mf/props :obj} - +(mf/defc import-form* + {::mf/forward-ref true} [{:keys [project-id on-finish-import]} external-ref] (let [on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file {:aria-hidden "true"} diff --git a/frontend/src/app/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs index ae0cd7b97d..1c47e099b9 100644 --- a/frontend/src/app/main/ui/dashboard/placeholder.cljs +++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs @@ -59,9 +59,9 @@ [:div {:class (stl/css :empty-project-card-subtitle)} (tr "dashboard.empty-project.explore")]] - [:& udi/import-form {:ref file-input - :project-id project-id - :on-finish-import on-finish-import}]])) + [:> udi/import-form* {:ref file-input + :project-id project-id + :on-finish-import on-finish-import}]])) (defn- make-has-other-files-or-projects-ref "Return a ref that resolves to true or false if there are at least some diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 4eddf3de91..00db6a7918 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -137,8 +137,7 @@ :top top :left left :options options}] - [:& udi/import-form {:ref file-input - :project-id (:id project) - :on-finish-import on-finish-import}]])) - + [:> udi/import-form* {:ref file-input + :project-id (:id project) + :on-finish-import on-finish-import}]])) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 5fb010fe25..eeda604490 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -1298,6 +1298,11 @@ (st/emit! (ev/event {::ev/name "explore-pricing-click" ::ev/origin "dashboard" :section "sidebar"})) (dom/open-new-window "https://penpot.app/pricing")))] + (mf/with-effect [teams] + (when (and (contains? cf/flags :nitrate) + (empty? teams)) + (st/emit! (dtm/fetch-teams)))) + (mf/with-effect [show-profile-menu?] (when-not show-profile-menu? (reset! sub-menu* nil))) diff --git a/frontend/src/app/main/ui/dashboard/subscription.cljs b/frontend/src/app/main/ui/dashboard/subscription.cljs index f207d735b2..bd33bfa998 100644 --- a/frontend/src/app/main/ui/dashboard/subscription.cljs +++ b/frontend/src/app/main/ui/dashboard/subscription.cljs @@ -7,7 +7,9 @@ [app.common.time :as ct] [app.config :as cf] [app.main.data.event :as ev] + [app.main.data.modal :as modal] [app.main.data.nitrate :as dnt] + [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] [app.main.ui.components.dropdown-menu :refer [dropdown-menu-item*]] @@ -119,27 +121,44 @@ (mf/defc nitrate-sidebar* [{:keys [profile teams]}] - (let [nitrate? (dnt/is-valid-license? profile) - nitrate-license (:subscription profile) - manual-license? (:manual nitrate-license) + (let [nitrate? (dnt/is-valid-license? profile) + nitrate-license (:subscription profile) + manual-license? (:manual nitrate-license) subscription-warning* (mf/use-state nil) - subscription-warning (deref subscription-warning*) - days-until-expiry (or (:days-until-expiry subscription-warning) - (:daysUntilExpiry subscription-warning) - (:days-from-expiry subscription-warning) - (:daysFromExpiry subscription-warning)) - expiration-date (or (:expiration-date subscription-warning) - (:expirationDate subscription-warning)) - expiration-date-text (when expiration-date - (ct/format-inst expiration-date "MMMM d")) - show-subscription-warning? (and manual-license? - (some? days-until-expiry) - (some? expiration-date-text)) - subscription-type (if nitrate? (:type nitrate-license) (get-subscription-type (-> profile :props :subscription))) + subscription-warning (deref subscription-warning*) + route (mf/deref refs/route) + route-name (get-in route [:data :name]) + + days-until-expiry + (or (:days-until-expiry subscription-warning) + (:daysUntilExpiry subscription-warning) + (:days-from-expiry subscription-warning) + (:daysFromExpiry subscription-warning)) + + expiration-date + (or (:expiration-date subscription-warning) + (:expirationDate subscription-warning)) + expiration-date-text + (when expiration-date + (ct/format-inst expiration-date "MMMM d")) + + show-subscription-warning? + (and nitrate? + manual-license? + (not= route-name :settings-subscription) + (some? days-until-expiry) + (some? expiration-date-text)) + + subscription-type + (if nitrate? (:type nitrate-license) (get-subscription-type (-> profile :props :subscription))) + + teams-loaded? (seq teams) + no-orgs-created? (mf/with-memo [teams] - (->> teams - vals - (not-any? :organization))) + (and (seq teams) + (->> teams + vals + (not-any? :organization)))) handle-click (mf/use-fn @@ -152,8 +171,8 @@ handle-go-to-cc (mf/use-fn dnt/go-to-nitrate-ac-create-org) - handle-go-to-subscription - (mf/use-fn #(st/emit! (rt/nav :settings-subscription)))] + handle-open-renew-modal + (mf/use-fn #(st/emit! (modal/show :nitrate-code-activation {:renew? true})))] (mf/with-effect [manual-license?] (if manual-license? @@ -163,7 +182,7 @@ [:* ;; TODO add translations for this texts when we have the definitive ones - (if (and nitrate? no-orgs-created? (not show-subscription-warning?)) + (if (and nitrate? teams-loaded? no-orgs-created? (not show-subscription-warning?)) ;; Banner for users with active nitrate license but no organizations created [:div {:class (stl/css :nitrate-banner :highlighted)} [:div {:class (stl/css :nitrate-content)} @@ -206,7 +225,7 @@ [:> button* {:variant "primary" :type "button" :class (stl/css :nitrate-bottom-button) - :on-click handle-go-to-subscription} + :on-click handle-open-renew-modal} (tr "subscription.dashboard.banner.renew")]]])])) (mf/defc nitrate-current-plan* diff --git a/frontend/src/app/main/ui/dashboard/templates.cljs b/frontend/src/app/main/ui/dashboard/templates.cljs index 32fab39dfc..b5a12d2dcf 100644 --- a/frontend/src/app/main/ui/dashboard/templates.cljs +++ b/frontend/src/app/main/ui/dashboard/templates.cljs @@ -91,8 +91,7 @@ [:span {:class (stl/css :title-icon)} arrow-icon]])]])) -(mf/defc card-item - {::mf/wrap-props false} +(mf/defc card-item* [{:keys [item index is-visible collapsed on-import]}] (let [id (dm/str "card-container-" index) href (u/join cf/public-uri (dm/str "images/thumbnails/template-" (:id item) ".jpg")) @@ -134,8 +133,7 @@ (:name item))] download-icon]]])) -(mf/defc card-item-link - {::mf/wrap-props false} +(mf/defc card-item-link* [{:keys [total is-visible collapsed section]}] (let [id (dm/str "card-container-" total) @@ -270,7 +268,7 @@ :ref content-ref} (for [index (range (count templates))] - [:& card-item + [:> card-item* {:on-import on-import-template :item (nth templates index) :index index @@ -278,7 +276,7 @@ :is-visible true :collapsed collapsed}]) - [:& card-item-link + [:> card-item-link* {:is-visible true :collapsed collapsed :section section diff --git a/frontend/src/app/main/ui/error_boundary.cljs b/frontend/src/app/main/ui/error_boundary.cljs index 60b42b45c0..226b87369b 100644 --- a/frontend/src/app/main/ui/error_boundary.cljs +++ b/frontend/src/app/main/ui/error_boundary.cljs @@ -37,8 +37,20 @@ ;; If the error is a stale-asset error (cross-build ;; module mismatch), force a hard page reload instead ;; of showing the error page to the user. - (if (errors/stale-asset-error? error) + (cond + (errors/stale-asset-error? error) (cf/throttled-reload :reason (ex-message error)) + + ;; If the error is known to be harmless (browser + ;; extensions, React DOM conflicts, etc.), ignore it + ;; silently — the global uncaught-error-handler + ;; already does this, but react-error-boundary's + ;; onError fires independently of the window.onerror + ;; pipeline, so we must also filter here. + (errors/is-ignorable-exception? error) + nil + + :else (do (set! errors/last-exception error) (ex/print-throwable error) diff --git a/frontend/src/app/main/ui/flex_controls.cljs b/frontend/src/app/main/ui/flex_controls.cljs index 4875d424bb..baa8207df3 100644 --- a/frontend/src/app/main/ui/flex_controls.cljs +++ b/frontend/src/app/main/ui/flex_controls.cljs @@ -11,6 +11,6 @@ [app.main.ui.flex-controls.margin :as fcm] [app.main.ui.flex-controls.padding :as fcp])) -(dm/export fcg/gap-control) -(dm/export fcm/margin-control) -(dm/export fcp/padding-control) +(dm/export fcg/gap-control*) +(dm/export fcm/margin-control*) +(dm/export fcp/padding-control*) diff --git a/frontend/src/app/main/ui/flex_controls/gap.cljs b/frontend/src/app/main/ui/flex_controls/gap.cljs index edef8fd144..710b89882f 100644 --- a/frontend/src/app/main/ui/flex_controls/gap.cljs +++ b/frontend/src/app/main/ui/flex_controls/gap.cljs @@ -27,9 +27,9 @@ [app.util.dom :as dom] [rumext.v2 :as mf])) -(mf/defc gap-display +(mf/defc gap-display* [{:keys [frame-id zoom gap-type gap on-pointer-enter on-pointer-leave - rect-data hover? selected? mouse-pos hover-value + rect-data is-hover is-selected mouse-pos hover-value on-move-selected on-context-menu on-change]}] (let [resizing (mf/use-var nil) start (mf/use-var nil) @@ -109,8 +109,8 @@ :on-pointer-down on-move-selected :on-context-menu on-context-menu - :style {:fill (if (or hover? selected?) fcc/distance-color "none") - :opacity (if selected? 0.5 0.25)}}] + :style {:fill (if (or is-hover is-selected) fcc/distance-color "none") + :opacity (if is-selected 0.5 0.25)}}] (let [handle-width (if (= axis :x) @@ -132,12 +132,12 @@ :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move :on-context-menu on-context-menu - :class (when (or hover? selected?) + :class (when (or is-hover is-selected) (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) - :style {:fill (if (or hover? selected?) fcc/distance-color "none") - :opacity (if selected? 0 1)}}])])) + :style {:fill (if (or is-hover is-selected) fcc/distance-color "none") + :opacity (if is-selected 0 1)}}])])) -(mf/defc gap-rects +(mf/defc gap-rects* [{:keys [frame zoom on-move-selected on-context-menu]}] (let [frame-id (:id frame) saved-dir (:layout-flex-dir frame) @@ -320,7 +320,7 @@ [:g.gaps {:pointer-events "visible"} (for [[index display-item] (d/enumerate (concat display-blocks display-children))] (let [gap-type (:gap-type display-item)] - [:& gap-display + [:> gap-display* {:key (str frame-id index) :frame-id frame-id :zoom zoom @@ -332,8 +332,8 @@ :on-context-menu on-context-menu :on-change on-change :rect-data display-item - :hover? (= @hover gap-type) - :selected? (= gap-selected gap-type) + :is-hover (= @hover gap-type) + :is-selected (= gap-selected gap-type) :mouse-pos mouse-pos :hover-value hover-value}])) @@ -349,12 +349,12 @@ :value @hover-value}])])) -(mf/defc gap-control +(mf/defc gap-control* [{:keys [frame zoom on-move-selected on-context-menu]}] (when frame [:g.measurement-gaps {:pointer-events "none"} [:g.hover-shapes - [:& gap-rects + [:> gap-rects* {:frame frame :zoom zoom :on-move-selected on-move-selected diff --git a/frontend/src/app/main/ui/flex_controls/margin.cljs b/frontend/src/app/main/ui/flex_controls/margin.cljs index c2aa537295..20f486fdeb 100644 --- a/frontend/src/app/main/ui/flex_controls/margin.cljs +++ b/frontend/src/app/main/ui/flex_controls/margin.cljs @@ -20,10 +20,10 @@ [app.util.dom :as dom] [rumext.v2 :as mf])) -(mf/defc margin-display - [{:keys [shape-id zoom hover-all? hover-v? hover-h? margin-num margin +(mf/defc margin-display* + [{:keys [shape-id zoom is-hover-all is-hover-v is-hover-h margin-num margin on-pointer-enter on-pointer-leave on-change - rect-data hover? selected? mouse-pos hover-value]}] + rect-data is-hover is-selected mouse-pos hover-value]}] (let [resizing? (mf/use-var false) start (mf/use-var nil) original-value (mf/use-var 0) @@ -42,7 +42,7 @@ calc-modifiers (mf/use-fn - (mf/deps shape-id margin-num margin hover-all? hover-v? hover-h?) + (mf/deps shape-id margin-num margin is-hover-all is-hover-v is-hover-h) (fn [pos] (let [delta (-> (gpt/to-vec @start pos) @@ -54,10 +54,10 @@ layout-item-margin (cond - hover-all? (assoc margin :m1 val :m2 val :m3 val :m4 val) - hover-v? (assoc margin :m1 val :m3 val) - hover-h? (assoc margin :m2 val :m4 val) - :else (assoc margin margin-num val)) + is-hover-all (assoc margin :m1 val :m2 val :m3 val :m4 val) + is-hover-v (assoc margin :m1 val :m3 val) + is-hover-h (assoc margin :m2 val :m4 val) + :else (assoc margin margin-num val)) layout-item-margin-type (if (= (:m1 margin) (:m2 margin) (:m3 margin) (:m4 margin)) :simple :multiple)] @@ -114,13 +114,13 @@ :on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move - :class (when (or hover? selected?) + :class (when (or is-hover is-selected) (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) - :style {:fill (if (or hover? selected?) fcc/warning-color "none") - :opacity (if selected? 0.5 0.25)}}])) + :style {:fill (if (or is-hover is-selected) fcc/warning-color "none") + :opacity (if is-selected 0.5 0.25)}}])) -(mf/defc margin-rects [{:keys [shape frame zoom alt? shift?]}] +(mf/defc margin-rects* [{:keys [shape frame zoom is-alt is-shift]}] (let [shape-id (:id shape) pill-width (/ fcc/flex-display-pill-width zoom) pill-height (/ fcc/flex-display-pill-height zoom) @@ -133,9 +133,9 @@ hover-value (mf/use-state 0) mouse-pos (mf/use-state nil) hover (mf/use-state nil) - hover-all? (and (not (nil? @hover)) alt?) - hover-v? (and (or (= @hover :m1) (= @hover :m3)) shift?) - hover-h? (and (or (= @hover :m2) (= @hover :m4)) shift?) + hover-all? (and (not (nil? @hover)) is-alt) + hover-v? (and (or (= @hover :m1) (= @hover :m3)) is-shift) + hover-h? (and (or (= @hover :m2) (= @hover :m4)) is-shift) margin (:layout-item-margin shape) {:keys [width height x1 x2 y1 y2]} (:selrect shape) @@ -202,21 +202,21 @@ [:g.margins {:pointer-events "visible"} (for [[margin-num rect-data] margin-display-data] - [:& margin-display + [:> margin-display* {:key (:key rect-data) :shape-id shape-id :zoom zoom - :hover-all? hover-all? - :hover-v? hover-v? - :hover-h? hover-h? + :is-hover-all hover-all? + :is-hover-v hover-v? + :is-hover-h hover-h? :margin-num margin-num :margin margin :on-pointer-enter (partial on-pointer-enter margin-num (get margin margin-num)) :on-pointer-leave on-pointer-leave :on-change on-change :rect-data rect-data - :hover? (hover? margin-num) - :selected? (get margins-selected margin-num) + :is-hover (hover? margin-num) + :is-selected (get margins-selected margin-num) :mouse-pos mouse-pos :hover-value hover-value}]) @@ -231,14 +231,14 @@ :y (- (:y @mouse-pos) pill-width) :value @hover-value}])])) -(mf/defc margin-control - [{:keys [shape parent zoom alt? shift?]}] +(mf/defc margin-control* + [{:keys [shape parent zoom is-alt is-shift]}] (when shape [:g.measurement-gaps {:pointer-events "none"} [:g.hover-shapes - [:& margin-rects + [:> margin-rects* {:shape shape :frame parent :zoom zoom - :alt? alt? - :shift? shift?}]]])) + :is-alt is-alt + :is-shift is-shift}]]])) diff --git a/frontend/src/app/main/ui/flex_controls/padding.cljs b/frontend/src/app/main/ui/flex_controls/padding.cljs index b2c5e4272b..b751c565d3 100644 --- a/frontend/src/app/main/ui/flex_controls/padding.cljs +++ b/frontend/src/app/main/ui/flex_controls/padding.cljs @@ -20,9 +20,9 @@ [app.util.dom :as dom] [rumext.v2 :as mf])) -(mf/defc padding-display - [{:keys [frame-id zoom hover-all? hover-v? hover-h? padding-num padding on-pointer-enter - on-pointer-leave rect-data hover? selected? mouse-pos hover-value on-move-selected +(mf/defc padding-display* + [{:keys [frame-id zoom is-hover-all is-hover-v is-hover-h padding-num padding on-pointer-enter + on-pointer-leave rect-data is-hover is-selected mouse-pos hover-value on-move-selected on-context-menu on-change]}] (let [resizing? (mf/use-var false) start (mf/use-var nil) @@ -42,7 +42,7 @@ calc-modifiers (mf/use-fn - (mf/deps frame-id padding-num padding hover-all? hover-v? hover-h?) + (mf/deps frame-id padding-num padding is-hover-all is-hover-v is-hover-h) (fn [pos] (let [delta (-> (gpt/to-vec @start pos) @@ -54,10 +54,10 @@ layout-padding (cond - hover-all? (assoc padding :p1 val :p2 val :p3 val :p4 val) - hover-v? (assoc padding :p1 val :p3 val) - hover-h? (assoc padding :p2 val :p4 val) - :else (assoc padding padding-num val)) + is-hover-all (assoc padding :p1 val :p2 val :p3 val :p4 val) + is-hover-v (assoc padding :p1 val :p3 val) + is-hover-h (assoc padding :p2 val :p4 val) + :else (assoc padding padding-num val)) layout-padding-type @@ -115,8 +115,8 @@ :on-pointer-move on-pointer-move :on-pointer-down on-move-selected :on-context-menu on-context-menu - :style {:fill (if (or hover? selected?) fcc/distance-color "none") - :opacity (if selected? 0.5 0.25)}}] + :style {:fill (if (or is-hover is-selected) fcc/distance-color "none") + :opacity (if is-selected 0.5 0.25)}}] (let [handle-width (if (= axis :x) @@ -139,17 +139,17 @@ :on-pointer-move on-pointer-move :on-context-menu on-context-menu :class - (when (or hover? selected?) + (when (or is-hover is-selected) (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) :style - {:fill (if (or hover? selected?) fcc/distance-color "none") - :opacity (if selected? 0 1)}}])])) + {:fill (if (or is-hover is-selected) fcc/distance-color "none") + :opacity (if is-selected 0 1)}}])])) -(mf/defc padding-rects - [{:keys [frame zoom alt? shift? on-move-selected on-context-menu]}] +(mf/defc padding-rects* + [{:keys [frame zoom is-alt is-shift on-move-selected on-context-menu]}] (let [frame-id (:id frame) paddings-selected (mf/deref refs/workspace-paddings-selected) current-modifiers (mf/use-state nil) @@ -161,9 +161,9 @@ mouse-pos (mf/use-state nil) hover (mf/use-state nil) - hover-all? (and (not (nil? @hover)) alt?) - hover-v? (and (or (= @hover :p1) (= @hover :p3)) shift?) - hover-h? (and (or (= @hover :p2) (= @hover :p4)) shift?) + hover-all? (and (not (nil? @hover)) is-alt) + hover-v? (and (or (= @hover :p1) (= @hover :p3)) is-shift) + hover-h? (and (or (= @hover :p2) (= @hover :p4)) is-shift) padding (:layout-padding frame) {:keys [width height x1 x2 y1 y2]} (:selrect frame) pill-width (/ fcc/flex-display-pill-width zoom) @@ -243,13 +243,13 @@ [:g.paddings {:pointer-events "visible"} (for [[padding-num rect-data] padding-rect-data] - [:& padding-display + [:> padding-display* {:key (:key rect-data) :frame-id frame-id :zoom zoom - :hover-all? hover-all? - :hover-v? hover-v? - :hover-h? hover-h? + :is-hover-all hover-all? + :is-hover-v hover-v? + :is-hover-h hover-h? :padding padding :mouse-pos mouse-pos :hover-value hover-value @@ -259,8 +259,8 @@ :on-move-selected on-move-selected :on-context-menu on-context-menu :on-change on-change - :hover? (hover? padding-num) - :selected? (get paddings-selected padding-num) + :is-hover (hover? padding-num) + :is-selected (get paddings-selected padding-num) :rect-data rect-data}]) (when @hover @@ -274,15 +274,15 @@ :y (- (:y @mouse-pos) pill-width) :value @hover-value}])])) -(mf/defc padding-control - [{:keys [frame zoom alt? shift? on-move-selected on-context-menu]}] +(mf/defc padding-control* + [{:keys [frame zoom is-alt is-shift on-move-selected on-context-menu]}] (when frame [:g.measurement-gaps {:pointer-events "none"} [:g.hover-shapes - [:& padding-rects + [:> padding-rects* {:frame frame :zoom zoom - :alt? alt? - :shift? shift? + :is-alt is-alt + :is-shift is-shift :on-move-selected on-move-selected :on-context-menu on-context-menu}]]])) diff --git a/frontend/src/app/main/ui/inspect/exports.cljs b/frontend/src/app/main/ui/inspect/exports.cljs index 00da7925fe..d431a5aeeb 100644 --- a/frontend/src/app/main/ui/inspect/exports.cljs +++ b/frontend/src/app/main/ui/inspect/exports.cljs @@ -9,6 +9,7 @@ (:require [app.common.data :as d] [app.main.data.exports.assets :as de] + [app.main.data.viewer :as dv] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.select :refer [select]] @@ -17,8 +18,12 @@ [app.util.dom :as dom] [app.util.i18n :refer [tr c]] [app.util.keyboard :as kbd] + [okulary.core :as l] [rumext.v2 :as mf])) +(def ^:private exports-cache-ref + (l/derived :inspect-exports-cache st/state)) + (mf/defc exports {::mf/wrap [#(mf/memo % =)]} [{:keys [shapes page-id file-id share-id type] :as props}] @@ -65,46 +70,58 @@ (de/request-export {:exports exports}) (de/export-shapes-event exports "viewer"))))) + shapes-key + (mf/use-memo (mf/deps shapes) #(vec (sort (map :id shapes)))) + add-export (mf/use-callback - (mf/deps shapes) + (mf/deps shapes exports) (fn [] (let [xspec {:type :png :suffix "" - :scale 1}] - (swap! exports conj xspec)))) + :scale 1} + new-exports (conj @exports xspec)] + (reset! exports new-exports) + (st/emit! (dv/update-exports-cache shapes-key new-exports))))) delete-export (mf/use-callback - (mf/deps shapes) + (mf/deps shapes exports) (fn [index] - (swap! exports (fn [exports] - (let [[before after] (split-at index exports)] - (d/concat-vec before (rest after))))))) + (let [new-exports (let [[before after] (split-at index @exports)] + (d/concat-vec before (rest after)))] + (reset! exports new-exports) + (st/emit! (dv/update-exports-cache shapes-key new-exports))))) on-scale-change (mf/use-callback - (mf/deps shapes) + (mf/deps shapes exports) (fn [index event] - (let [scale (d/parse-double event)] - (swap! exports assoc-in [index :scale] scale)))) + (let [scale (d/parse-double event) + new-exports (assoc-in @exports [index :scale] scale)] + (reset! exports new-exports) + (st/emit! (dv/update-exports-cache shapes-key new-exports))))) on-suffix-change (mf/use-callback - (mf/deps shapes) + (mf/deps shapes exports) (fn [event] (let [value (dom/get-target-val event) index (-> (dom/get-current-target event) (dom/get-data "value") - (d/parse-integer))] - (swap! exports assoc-in [index :suffix] value)))) + (d/parse-integer)) + new-exports (assoc-in @exports [index :suffix] value)] + (reset! exports new-exports) + (st/emit! (dv/update-exports-cache shapes-key new-exports))))) on-type-change (mf/use-callback - (mf/deps shapes) + (mf/deps shapes exports) (fn [index event] - (let [type (keyword event)] - (swap! exports assoc-in [index :type] type)))) + (let [type (keyword event) + new-exports (assoc-in @exports [index :type] type)] + (reset! exports new-exports) + (st/emit! (dv/update-exports-cache shapes-key new-exports))))) manage-key-down (mf/use-callback @@ -130,10 +147,14 @@ (mf/use-effect (mf/deps shapes) (fn [] - (reset! exports (-> (mapv #(:exports % []) shapes) - flatten - distinct - vec)))) + (let [shapes-key (vec (sort (map :id shapes))) + cached (get @exports-cache-ref shapes-key)] + (if (some? cached) + (reset! exports cached) + (reset! exports (->> shapes + (mapcat #(:exports % [])) + (distinct) + vec)))))) [:div {:class (stl/css :element-set)} [:div {:class (stl/css :element-title)} [:> title-bar* {:collapsable false diff --git a/frontend/src/app/main/ui/inspect/render.cljs b/frontend/src/app/main/ui/inspect/render.cljs index 87cdb901f7..25e223f9e5 100644 --- a/frontend/src/app/main/ui/inspect/render.cljs +++ b/frontend/src/app/main/ui/inspect/render.cljs @@ -23,7 +23,7 @@ [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] - [app.main.ui.viewer.interactions :refer [prepare-objects]] + [app.main.ui.viewer.viewport-common :refer [prepare-objects]] [app.util.dom :as dom] [app.util.object :as obj] [rumext.v2 :as mf])) diff --git a/frontend/src/app/main/ui/notifications/context_notification.cljs b/frontend/src/app/main/ui/notifications/context_notification.cljs index e66c16592c..2c07d5218d 100644 --- a/frontend/src/app/main/ui/notifications/context_notification.cljs +++ b/frontend/src/app/main/ui/notifications/context_notification.cljs @@ -54,16 +54,15 @@ ;; The content can arrive in markdown format, in these cases ;; we will use the prop is-html to true to indicate it and ;; that the html injection is performed and the necessary css classes are applied. - [:div {:class (stl/css :context-text) - :dangerouslySetInnerHTML (when is-html #js {:__html content})} - (when-not is-html - [:* - content - (when (some? links) - (for [[index link] (d/enumerate links)] - ;; TODO Review this component - [:> lb/link-button* {:class (stl/css :link) - :on-click (:callback link) - :value (:label link) - :key (dm/str "link-" index)}]))])]]) - + (if is-html + [:div {:class (stl/css :context-text) + :dangerouslySetInnerHTML #js {:__html content}}] + [:div {:class (stl/css :context-text)} + content + (when (some? links) + (for [[index link] (d/enumerate links)] + ;; TODO Review this component + [:> lb/link-button* {:class (stl/css :link) + :on-click (:callback link) + :value (:label link) + :key (dm/str "link-" index)}]))])]) diff --git a/frontend/src/app/main/ui/onboarding/questions.cljs b/frontend/src/app/main/ui/onboarding/questions.cljs index d3974ed2f2..64fff7896e 100644 --- a/frontend/src/app/main/ui/onboarding/questions.cljs +++ b/frontend/src/app/main/ui/onboarding/questions.cljs @@ -208,53 +208,28 @@ [:and [:map {:title "QuestionsFormStep3"} [:team-size - [:enum "more-than-50" "31-50" "11-30" "2-10" "freelancer" "personal-project"]] - - [:planning ::sm/text] - - [:planning-other {:optional true} - [::sm/text {:max 512}]]] - - [:fn {:error/field :planning-other} - (fn [{:keys [planning planning-other]}] - (or (not= planning "other") - (and (= planning "other") - (not (str/blank? planning-other)))))]]) + [:enum "1" "2-100" "101-500" "501-1000" "1001-5000" "5001+"]]]]) (mf/defc step-3 {::mf/props :obj} [{:keys [on-next on-prev form show-step-3]}] (let [team-size-options (mf/with-memo [] - [{:label (tr "labels.select-option") :value "" :key "team-size" :disabled true} - {:label (tr "onboarding.questions.team-size.more-than-50") :value "more-than-50" :key "more-than-50"} - {:label (tr "onboarding.questions.team-size.31-50") :value "31-50" :key "31-50"} - {:label (tr "onboarding.questions.team-size.11-30") :value "11-30" :key "11-30"} - {:label (tr "onboarding.questions.team-size.2-10") :value "2-10" :key "2-10"} - {:label (tr "onboarding.questions.team-size.freelancer") :value "freelancer" :key "freelancer"} - {:label (tr "onboarding.questions.team-size.personal-project") :value "personal-project" :key "personal-project"}]) - - planning-options - (mf/with-memo [] - (-> (shuffle [{:label (tr "labels.select-option") - :value "" :key "questions:what-brings-you-here" - :disabled true} - {:label (tr "onboarding.questions.reasons.exploring") - :value "discover-more-about-penpot" - :key "discover-more-about-penpot"} - {:label (tr "onboarding.questions.reasons.fit") - :value "test-penpot-to-see-if-its-a-fit-for-team" - :key "test-penpot-to-see-if-its-a-fit-for-team"} - {:label (tr "onboarding.questions.reasons.alternative") - :value "alternative-to-figma" - :key "alternative-to-figma"} - {:label (tr "onboarding.questions.reasons.testing") - :value "try-out-before-using-penpot-on-premise" - :key "try-out-before-using-penpot-on-premise"}]) - (conj {:label (tr "labels.other-short") :value "other"}))) - - current-planning - (dm/get-in @form [:data :planning])] + [{:label (tr "labels.select-option") + :value "" :key "team-size" + :disabled true} + {:label (tr "onboarding.questions.team-size.just-me") + :value "1" :key "1"} + {:label (tr "onboarding.questions.team-size.2-100") + :value "2-100" :key "2-100"} + {:label (tr "onboarding.questions.team-size.101-500") + :value "101-500" :key "101-500"} + {:label (tr "onboarding.questions.team-size.501-1000") + :value "501-1000" :key "501-1000"} + {:label (tr "onboarding.questions.team-size.1001-5000") + :value "1001-5000" :key "1001-5000"} + {:label (tr "onboarding.questions.team-size.more-than-5001") + :value "5001+" :key "5001+"}])] [:& step-container {:form form :step 3 @@ -267,26 +242,8 @@ [:h1 {:class (stl/css :modal-title)} (tr "onboarding.questions.step3.title")] - [:div {:class (stl/css :modal-question)} - [:h3 {:class (stl/css :modal-subtitle)} - (tr "onboarding.questions.step1.question2")] - - [:& fm/select - {:options planning-options - :select-class (stl/css :select-class) - :default "" - :name :planning - :dropdown-class (stl/css :question-dropdown)}]] - - (when (= current-planning "other") - [:& fm/input {:name :planning-other - :class (stl/css :input-spacing) - :placeholder (tr "labels.other") - :show-error false - :label ""}]) [:div {:class (stl/css :modal-question)} - [:h3 {:class (stl/css :modal-subtitle)} (tr "onboarding.questions.step3.question3")] [:& fm/select {:options team-size-options :default "" :select-class (stl/css :select-class) diff --git a/frontend/src/app/main/ui/onboarding/questions.scss b/frontend/src/app/main/ui/onboarding/questions.scss index 9265788c98..4438dcc1fb 100644 --- a/frontend/src/app/main/ui/onboarding/questions.scss +++ b/frontend/src/app/main/ui/onboarding/questions.scss @@ -119,7 +119,7 @@ // STEP-3 .step-3 { - grid-template-rows: deprecated.$s-20 auto auto auto auto deprecated.$s-32; + grid-template-rows: deprecated.$s-20 auto auto deprecated.$s-32; } .image-radio { diff --git a/frontend/src/app/main/ui/onboarding/team_choice.cljs b/frontend/src/app/main/ui/onboarding/team_choice.cljs index 65cb48546a..af457b2dd3 100644 --- a/frontend/src/app/main/ui/onboarding/team_choice.cljs +++ b/frontend/src/app/main/ui/onboarding/team_choice.cljs @@ -20,9 +20,8 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) -(mf/defc left-sidebar - {::mf/props :obj - ::mf/private true} +(mf/defc left-sidebar* + {::mf/private true} [] [:div {:class (stl/css :modal-left)} [:h2 {:class (stl/css :modal-subtitle)} @@ -63,9 +62,8 @@ [:role :keyword] [:emails {:optional true} [::sm/set ::sm/email]]]) -(mf/defc team-form - {::mf/props :obj - ::mf/private true} +(mf/defc team-form* + {::mf/private true} [{:keys [go-to-team]}] (let [initial (mf/with-memo [] {:role "editor"}) @@ -228,8 +226,7 @@ :on-click on-skip} (tr "onboarding.choice.team-up.continue-without-a-team")]]]]])) -(mf/defc onboarding-team-modal - {::mf/props :obj} +(mf/defc onboarding-team-modal* [{:keys [go-to-team]}] [:div {:class (stl/css-case @@ -239,7 +236,7 @@ [:h1 {:class (stl/css :modal-title)} (tr "onboarding-v2.welcome.title")] [:div {:class (stl/css :modal-sections)} - [:& left-sidebar] + [:> left-sidebar*] [:div {:class (stl/css :separator)}] - [:& team-form {:go-to-team go-to-team}]]]]) + [:> team-form* {:go-to-team go-to-team}]]]]) diff --git a/frontend/src/app/main/ui/onboarding/templates.cljs b/frontend/src/app/main/ui/onboarding/templates.cljs index d6d7a95135..3fccd1dc60 100644 --- a/frontend/src/app/main/ui/onboarding/templates.cljs +++ b/frontend/src/app/main/ui/onboarding/templates.cljs @@ -19,7 +19,7 @@ [beicon.v2.core :as rx] [rumext.v2 :as mf])) -(mf/defc template-item +(mf/defc template-item* [{:keys [name path image project-id]}] (let [downloading? (mf/use-state false) link (dm/str (assoc cf/public-uri :path path)) @@ -78,12 +78,12 @@ [:p (tr "onboarding.templates.subtitle")] [:div.templates - [:& template-item + [:> template-item* {:path "/github/penpot-files/Penpot-Design-system.penpot" :image "https://penpot.app/images/libraries/cover-ds-penpot.jpg" :name "Penpot Design System" :project-id project-id}] - [:& template-item + [:> template-item* {:path "/github/penpot-files/Material-Design-Kit.penpot" :image "https://penpot.app/images/libraries/cover-material.jpg" :name "Material Design Kit" diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 50a5dda64f..57665fc9d8 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -18,15 +18,15 @@ [app.main.ui.settings.feedback :refer [feedback-page*]] [app.main.ui.settings.integrations :refer [integrations-page*]] [app.main.ui.settings.notifications :refer [notifications-page*]] - [app.main.ui.settings.options :refer [options-page]] - [app.main.ui.settings.password :refer [password-page]] - [app.main.ui.settings.profile :refer [profile-page]] - [app.main.ui.settings.sidebar :refer [sidebar]] + [app.main.ui.settings.options :refer [options-page*]] + [app.main.ui.settings.password :refer [password-page*]] + [app.main.ui.settings.profile :refer [profile-page*]] + [app.main.ui.settings.sidebar :refer [sidebar*]] [app.main.ui.settings.subscription :refer [subscription-page*]] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) -(mf/defc header +(mf/defc header* {::mf/wrap [mf/memo]} [] [:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"} @@ -49,15 +49,15 @@ [:section {:class (stl/css :dashboard-layout-refactor :dashboard)} - [:& sidebar {:profile profile - :section section}] + [:> sidebar* {:profile profile + :section section}] [:div {:class (stl/css :dashboard-content)} - [:& header] + [:> header*] [:section {:class (stl/css :dashboard-container)} (case section :settings-profile - [:& profile-page] + [:> profile-page*] :settings-feedback [:> feedback-page* {:type type @@ -65,10 +65,10 @@ :error-href error-href}] :settings-password - [:& password-page] + [:> password-page*] :settings-options - [:& options-page] + [:> options-page*] :settings-subscription [:> subscription-page* {:profile profile}] @@ -77,10 +77,9 @@ [:> integrations-page*] :settings-notifications - [:& notifications-page* {:profile profile}])]]]])) + [:> notifications-page* {:profile profile}])]]]])) (mf/defc settings-page* {::mf/lazy-load true} [props] [:> settings* props]) - diff --git a/frontend/src/app/main/ui/settings/integrations.cljs b/frontend/src/app/main/ui/settings/integrations.cljs index 50b5bd57bd..e7d51438bc 100644 --- a/frontend/src/app/main/ui/settings/integrations.cljs +++ b/frontend/src/app/main/ui/settings/integrations.cljs @@ -406,18 +406,16 @@ (mf/defc mcp-server-section* {::mf/private true} [] - (let [tokens (mf/deref refs/access-tokens) - profile (mf/deref refs/profile) + (let [tokens (mf/deref refs/access-tokens) + profile (mf/deref refs/profile) + mcp-key-expired? (mf/deref refs/mcp-key-expired?) mcp-key (some #(when (= (:type %) "mcp") %) tokens) mcp-token (:token mcp-key "") mcp-url (dm/str cf/mcp-server-url "?userToken=" mcp-token) mcp-enabled? (true? (-> profile :props :mcp-enabled)) - expires-at (:expires-at mcp-key) - expired? (and (some? expires-at) (> (ct/now) expires-at)) - - show-enabled? (and mcp-enabled? (false? expired?)) + show-enabled? (and mcp-enabled? (false? mcp-key-expired?)) tooltip-id (mf/use-id) @@ -494,7 +492,7 @@ (tr "integrations.mcp-server.status")] [:div {:class (stl/css :mcp-server-block)} - (when expired? + (when mcp-key-expired? [:> notification-pill* {:level :error :type :context} [:div {:class (stl/css :mcp-server-notification)} @@ -517,7 +515,7 @@ (when (and (false? mcp-enabled?) (nil? mcp-key)) [:div {:class (stl/css :mcp-server-switch-cover) :on-click handle-generate-mcp-key}]) - (when (true? expired?) + (when (true? mcp-key-expired?) [:div {:class (stl/css :mcp-server-switch-cover) :on-click handle-regenerate-mcp-key}])]]] diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index d49d676258..aca2ef130c 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -41,8 +41,7 @@ (st/emit! (du/update-profile data) (du/persist-profile {:on-success on-success})))) -(mf/defc options-form - {::mf/wrap-props false} +(mf/defc options-form* [] (let [profile (mf/deref refs/profile) initial (mf/with-memo [profile] @@ -116,7 +115,7 @@ :on-change handle-render-change}]] [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]])) -(mf/defc options-page +(mf/defc options-page* [] (let [profile (mf/deref refs/profile) renderer (or (-> profile :props :renderer) :svg)] @@ -127,7 +126,6 @@ [:* [:div {:class (stl/css :form-container) :data-testid "settings-form"} [:h2 (tr "labels.settings")] - [:& options-form {}]] + [:> options-form*]] (when (contains? cf/flags :render-switch) [:> webgl-settings* {:renderer renderer}])]])) - diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index 9cd32e2bdd..301bff0d00 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -59,7 +59,7 @@ (fn [{:keys [password-1 password-2]}] (= password-1 password-2))]]) -(mf/defc password-form +(mf/defc password-form* [] (let [initial (mf/with-memo [] {:password-old "" @@ -99,7 +99,7 @@ ;; --- Password Page -(mf/defc password-page +(mf/defc password-page* [] (mf/with-effect [] (dom/set-html-title (tr "title.settings.password"))) @@ -107,4 +107,4 @@ [:section {:class (stl/css :dashboard-settings)} [:div {:class (stl/css :form-container)} [:h2 (tr "dashboard.password-change")] - [:& password-form]]]) + [:> password-form*]]]) diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 3bb433aa77..124597a4e1 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -37,7 +37,7 @@ ;; --- Profile Form -(mf/defc profile-form +(mf/defc profile-form* {::mf/private true} [] (let [profile (mf/deref refs/profile) @@ -87,7 +87,7 @@ ;; --- Profile Photo Form -(mf/defc profile-photo-form +(mf/defc profile-photo-form* {::mf/private true} [] (let [input-ref (mf/use-ref nil) @@ -138,7 +138,7 @@ ;; --- Profile Page -(mf/defc profile-page +(mf/defc profile-page* [] (mf/with-effect [] (dom/set-html-title (tr "title.settings.profile"))) @@ -146,6 +146,5 @@ [:div {:class (stl/css :dashboard-settings)} [:div {:class (stl/css :form-container)} [:h2 (tr "labels.profile")] - [:& profile-photo-form] - [:& profile-form]]]) - + [:> profile-photo-form*] + [:> profile-form*]]]) diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index e2d1debdd4..94c634004f 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -57,8 +57,7 @@ (st/emit! (modal/show {:type :onboarding})) (st/emit! (modal/show {:type :release-notes :version version}))))) -(mf/defc sidebar-content - {::mf/props :obj} +(mf/defc sidebar-content* [{:keys [profile section]}] (let [profile? (= section :settings-profile) password? (= section :settings-password) @@ -135,12 +134,10 @@ feedback-icon [:span {:class (stl/css :element-title)} (tr "labels.contact-us")]])]]])) -(mf/defc sidebar - {::mf/wrap [mf/memo] - ::mf/props :obj} +(mf/defc sidebar* + {::mf/wrap [mf/memo]} [{:keys [profile section]}] [:div {:class (stl/css :dashboard-sidebar :settings)} - [:& sidebar-content {:profile profile - :section section}] + [:> sidebar-content* {:profile profile + :section section}] [:> profile-section* {:profile profile}]]) - diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 52923640cb..86838c5790 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -91,18 +91,17 @@ :bottom-link (not (or has-trial? code-action))) :on-click cta-link} cta-text]) (when code-action - [:button {:class (stl/css-case :cta-button true - :activate-by-code (= code-action :activate) - :renew-by-code (= code-action :renovate) - :bottom-link (= code-action :renovate)) - :on-click (cond - (= code-action :activate) - #(st/emit! (modal/show {:type :nitrate-code-activation})) - (= code-action :renovate) - #(st/emit! (modal/show :nitrate-code-activation {:renew? true})))} - (if (= code-action :activate) - (tr "subscription.settings.activate-by-code") - (tr "nitrate.subscription.settings.renew-with-code"))]) + (if (= code-action :activate) + [:button {:class (stl/css :cta-button :activate-by-code) + :on-click #(st/emit! (modal/show {:type :nitrate-code-activation}))} + (tr "subscription.settings.activate-by-code")] + + [:> button* {:variant "primary" + :type "button" + :class (stl/css :renew-by-code :bottom-link) + :on-click #(st/emit! (modal/show :nitrate-code-activation {:renew? true}))} + (tr "nitrate.subscription.settings.renew-with-code")])) + (when inline-error [:p {:class (stl/css :inline-error)} inline-error])])) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index f86208a8b7..2f997ac7ee 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -53,9 +53,9 @@ (defn- calculate-size "Calculate the total size we must reserve for the frame, including possible paddings - added because shadows or blur." + added because shadows, blur, or strokes." [objects frame zoom] - (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] + (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame {:ignore-margin? false})] {:base-width width :base-height height :x x @@ -186,7 +186,7 @@ :style {:width (:width size) :height (:height size) :position "fixed"}} - [:& interactions/viewport + [:> interactions/viewport* {:frame overlay-frame :base-frame frame :frame-offset overlay-position @@ -201,7 +201,7 @@ :height (:height size) :left (* (:x overlay-position) zoom) :top (* (:y overlay-position) zoom)}} - [:& interactions/viewport + [:> interactions/viewport* {:frame overlay-frame :base-frame frame :frame-offset overlay-position @@ -236,7 +236,7 @@ :height (:height orig-size) :position "relative"}} - [:& interactions/viewport + [:> interactions/viewport* {:frame orig-frame :base-frame orig-frame :frame-offset (gpt/point 0 0) @@ -251,7 +251,7 @@ :height (:height size) :position "relative"}} - [:& interactions/viewport + [:> interactions/viewport* {:frame frame :base-frame frame :frame-offset (gpt/point 0 0) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 35138ef829..19059f5fde 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -9,72 +9,39 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] - [app.common.types.modifiers :as ctm] [app.common.types.page :as ctp] [app.common.uuid :as uuid] + [app.config :as cf] [app.main.data.comments :as dcm] [app.main.data.viewer :as dv] + [app.main.features :as features] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.hooks :as h] [app.main.ui.icons :as deprecated-icon] [app.main.ui.viewer.shapes :as shapes] + [app.main.ui.viewer.viewport-common :as vpc] + [app.main.ui.viewer.viewport-wasm :as viewport.wasm] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [goog.events :as events] [rumext.v2 :as mf])) -(defn prepare-objects - [frame size delta objects] - (let [frame-id (:id frame) - vector (-> (gpt/point (:x size) (:y size)) - (gpt/add delta) - (gpt/negate)) - update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] - (->> (cfh/get-children-ids objects frame-id) - (into [frame-id]) - (reduce update-fn objects)))) - -(defn get-fixed-ids - [objects] - (let [fixed-ids (filter cfh/fixed-scroll? (vals objects)) - - ;; we have to consider the children if the fixed element is a group - fixed-children-ids - (into #{} (mapcat #(cfh/get-children-ids objects (:id %)) fixed-ids)) - - parent-children-ids - (->> fixed-ids - (mapcat #(cons (:id %) (cfh/get-parent-ids objects (:id %)))) - (remove #(= % uuid/zero))) - - fixed-ids - (concat fixed-children-ids parent-children-ids)] - fixed-ids)) - -(mf/defc viewport-svg - {::mf/wrap [mf/memo] - ::mf/wrap-props false} - [props] - (let [page (unchecked-get props "page") - frame (unchecked-get props "frame") - base (unchecked-get props "base") - offset (unchecked-get props "offset") - size (unchecked-get props "size") - fixed? (unchecked-get props "fixed?") - delta (or (unchecked-get props "delta") (gpt/point 0 0)) +(mf/defc viewport-svg* + {::mf/wrap [mf/memo]} + [{:keys [page frame base offset size is-fixed delta]}] + (let [delta (or delta (gpt/point 0 0)) vbox (:vbox size) + is-fixed (true? is-fixed) - frame (cond-> frame fixed? (assoc :fixed-scroll true)) + frame (cond-> frame is-fixed (assoc :fixed-scroll true)) objects (:objects page) - objects (cond-> objects fixed? (assoc-in [(:id frame) :fixed-scroll] true)) + objects (cond-> objects is-fixed (assoc-in [(:id frame) :fixed-scroll] true)) - fixed-ids (get-fixed-ids objects) + fixed-ids (vpc/get-fixed-ids objects) not-fixed-ids (->> (remove (set fixed-ids) (keys objects)) @@ -86,7 +53,7 @@ (map (d/getf objects)) (concat [frame]) (d/index-by :id) - (prepare-objects frame size delta))) + (vpc/prepare-objects frame size delta))) objects-fixed (mf/with-memo [fixed-ids page frame size delta] @@ -123,7 +90,7 @@ [:& (mf/provider shapes/base-frame-ctx) {:value base} [:& (mf/provider shapes/frame-offset-ctx) {:value offset} - (if fixed? + (if is-fixed [:svg {:class (stl/css :fixed) :view-box vbox :width (:width size) @@ -159,23 +126,20 @@ :fill "none"} [:& wrapper-not-fixed {:shape frame :view-box vbox}]]])]])) -(mf/defc viewport - {::mf/wrap [mf/memo] - ::mf/wrap-props false} - [props] +(mf/defc viewport* + {::mf/wrap [mf/memo]} + [{:keys [interactions-mode frame-offset size delta page frame base-frame is-fixed]}] (let [;; NOTE: with `use-equal-memo` hook we ensure that all values ;; conserves the reference identity for avoid unnecessary ;; dummy rerenders. - mode (h/use-equal-memo (unchecked-get props "interactions-mode")) - offset (h/use-equal-memo (unchecked-get props "frame-offset")) - size (h/use-equal-memo (unchecked-get props "size")) - delta (unchecked-get props "delta") + mode (h/use-equal-memo interactions-mode) + offset (h/use-equal-memo frame-offset) + size (h/use-equal-memo size) + base base-frame - page (unchecked-get props "page") - frame (unchecked-get props "frame") - base (unchecked-get props "base-frame") - fixed? (unchecked-get props "fixed?")] + render-wasm? (and (features/use-feature "render-wasm/v1") + (contains? cf/flags :available-viewer-wasm))] (mf/with-effect [mode] (let [on-click @@ -210,13 +174,21 @@ (events/unlistenByKey key2) (events/unlistenByKey key3)))) - [:& viewport-svg {:page page - :frame frame - :base base - :offset offset - :size size - :delta delta - :fixed? fixed?}])) + (if ^boolean render-wasm? + [:> viewport.wasm/viewport-wasm* {:page page + :frame frame + :base base + :offset offset + :size size + :delta delta + :is-fixed is-fixed}] + [:> viewport-svg* {:page page + :frame frame + :base base + :offset offset + :size size + :delta delta + :is-fixed is-fixed}]))) (mf/defc flows-menu* {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 880c515c49..425ed01d18 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -294,6 +294,74 @@ :pointer-events "none" :transform (gsh/transform-str shape)}]))) +;; --- WASM viewer hotspots --- +;; In WASM viewer mode the frame pixels come from a WASM snapshot, so we don't +;; render the SVG visuals at all. We only render the actionable areas (hotspots) +;; on top of the image: a transparent hit/highlight rect per interactive shape, +;; wired to the same interaction handlers as the regular SVG tree. + +(mf/defc hotspot* + [{:keys [shape all-objects]}] + (let [base-frame (mf/use-ctx base-frame-ctx) + frame-offset (mf/use-ctx frame-offset-ctx) + show-interactions (mf/deref ref:viewer-show-interactions) + overlays (mf/deref refs/viewer-overlays) + interactions (:interactions shape) + {:keys [x y width height]} (:selrect shape) + + on-pd (mf/use-fn (mf/deps shape base-frame frame-offset all-objects overlays) + #(on-pointer-down % shape base-frame frame-offset all-objects overlays)) + on-pu (mf/use-fn (mf/deps shape base-frame frame-offset all-objects overlays) + #(on-pointer-up % shape base-frame frame-offset all-objects overlays)) + on-pe (mf/use-fn (mf/deps shape base-frame frame-offset all-objects overlays) + #(on-pointer-enter % shape base-frame frame-offset all-objects overlays)) + on-pl (mf/use-fn (mf/deps shape base-frame frame-offset all-objects overlays) + #(on-pointer-leave % shape base-frame frame-offset all-objects overlays))] + + (mf/with-effect [] + (let [sems (on-load shape base-frame frame-offset all-objects overlays)] + (partial run! tm/dispose! sems))) + + [:g {:style {:cursor (when (ctsi/actionable? interactions) "pointer")} + :on-pointer-down on-pd + :on-pointer-up on-pu + :on-pointer-enter on-pe + :on-pointer-leave on-pl} + [:rect {:x (- x 1) + :y (- y 1) + :width (+ width 2) + :height (+ height 2) + :fill "var(--color-accent-tertiary)" + :stroke "var(--color-accent-tertiary)" + :stroke-width (if show-interactions 1 0) + :fill-opacity (if show-interactions 0.2 0) + ;; This rect is the only hit target, so it must always capture + ;; pointer events even when fully transparent. + :pointer-events "all" + :transform (gsh/transform-str shape)}]])) + +(mf/defc frame-hotspots* + "Renders interaction hotspots for a frame subtree (WASM viewer mode). + `objects` must be the prepared (vbox-space) objects and `frame` the prepared + frame; only shapes with interactions produce a hotspot. + + Optional `shape-filter` is a predicate that receives the shape id and returns + true when it should be included (used to split fixed-scroll vs normal layers)." + [{:keys [objects all-objects shape-filter frame]}] + (let [all-objects (or all-objects objects) + frame-id (:id frame) + ids (cond->> (cons frame-id (cfh/get-children-ids objects frame-id)) + shape-filter (filter shape-filter)) + hotspots (->> ids + (keep #(get objects %)) + (filter (fn [s] (and (not (:hidden s)) + (seq (:interactions s))))))] + [:g {} + (for [shape hotspots] + [:> hotspot* {:key (str (:id shape)) + :shape shape + :all-objects all-objects}])])) + ;; TODO: use-memo use-fn diff --git a/frontend/src/app/main/ui/viewer/viewport_common.cljs b/frontend/src/app/main/ui/viewer/viewport_common.cljs new file mode 100644 index 0000000000..e2ebb1b881 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/viewport_common.cljs @@ -0,0 +1,67 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns app.main.ui.viewer.viewport-common + "Shared object preparation for viewer viewports (SVG and WASM)." + (:require + [app.common.data :as d] + [app.common.files.helpers :as cfh] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.types.modifiers :as ctm] + [app.common.uuid :as uuid])) + +(defn prepare-objects + [frame size delta objects] + (let [frame-id (:id frame) + vector (-> (gpt/point (:x size) (:y size)) + (gpt/add delta) + (gpt/negate)) + update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] + (->> (cfh/get-children-ids objects frame-id) + (into [frame-id]) + (reduce update-fn objects)))) + +(defn get-fixed-ids + [objects] + (let [fixed-ids (filter cfh/fixed-scroll? (vals objects)) + + fixed-children-ids + (into #{} (mapcat #(cfh/get-children-ids objects (:id %)) fixed-ids)) + + parent-children-ids + (->> fixed-ids + (mapcat #(cons (:id %) (cfh/get-parent-ids objects (:id %)))) + (remove #(= % uuid/zero))) + + fixed-ids + (concat fixed-children-ids parent-children-ids)] + fixed-ids)) + +(defn frame-fixed-mask-ids + "Fixed-layer shape ids inside `frame-id` (same rules as `get-fixed-ids`)." + [objects frame-id] + (when frame-id + (let [subtree (into #{} (cfh/get-children-ids-with-self objects frame-id))] + (into #{} + (filter #(contains? subtree %) + (get-fixed-ids objects)))))) + +(defn prepare-page-objects + "Transform all page objects into vbox-space (for overlay positioning)." + [objects size delta] + (let [vector (-> (gpt/point (:x size) (:y size)) + (gpt/add delta) + (gpt/negate)) + update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector)) + ids (->> (keys objects) (remove #(= % uuid/zero)))] + (reduce update-fn objects ids))) + +(defn viewer-scale + [size] + (if (and (:base-width size) (pos? (:base-width size))) + (/ (:width size) (:base-width size)) + 1)) diff --git a/frontend/src/app/main/ui/viewer/viewport_wasm.cljs b/frontend/src/app/main/ui/viewer/viewport_wasm.cljs new file mode 100644 index 0000000000..23d5b13490 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/viewport_wasm.cljs @@ -0,0 +1,163 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns app.main.ui.viewer.viewport-wasm + (:require-macros [app.main.style :as stl]) + (:require + [app.common.files.helpers :as cfh] + [app.common.geom.point :as gpt] + [app.main.render-viewer-wasm :as rwv] + [app.main.ui.viewer.shapes :as shapes] + [app.main.ui.viewer.viewport-common :as vpc] + [app.render-wasm.api :as wasm.api] + [rumext.v2 :as mf])) + +(defn- canvas-dimensions + "Physical canvas pixels (CSS layout size × DPR), matching the workspace WASM path." + [scale size] + (let [css-w (js/Math.round (* scale (:base-width size))) + css-h (js/Math.round (* scale (:base-height size))) + dpr (wasm.api/get-dpr)] + {:width (js/Math.round (* css-w dpr)) + :height (js/Math.round (* css-h dpr))})) + +(mf/defc wasm-hotspots-svg* + [{:keys [vbox size class prepared prepared-all prepared-frame shape-filter]}] + [:svg {:view-box vbox + :width (:width size) + :height (:height size) + :version "1.1" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns "http://www.w3.org/2000/svg" + :fill "none" + :style {:position "absolute" + :top 0 + :left 0} + :class class} + [:> shapes/frame-hotspots* + {:objects prepared + :all-objects prepared-all + :frame prepared-frame + :shape-filter shape-filter}]]) + +(mf/defc wasm-layer* + [{:keys [canvas-ref scale size vbox + prepared prepared-all prepared-frame class shape-filter]}] + (let [{:keys [width height]} (canvas-dimensions scale size)] + [:div {:style {:position "absolute" + :top 0 + :left 0}} + [:canvas {:ref canvas-ref :width width :height height :style {:width "100%" + :height "100%" + :background "transparent" + :pointer-events "none"}}] + [:> wasm-hotspots-svg* {:vbox vbox + :size size + :class class + :prepared prepared + :prepared-all prepared-all + :prepared-frame prepared-frame + :shape-filter shape-filter}]])) + +(defn- fixed-scroll-layer-ids + [objects frame-id has-fixed?] + (let [frame-subtree-ids (into #{} (cfh/get-children-ids-with-self objects frame-id)) + fixed-mask-ids (when has-fixed? (vpc/frame-fixed-mask-ids objects frame-id)) + fixed-mask-set (or fixed-mask-ids #{}) + not-fixed-include-ids + (when has-fixed? + (into [] + (distinct + (conj (->> frame-subtree-ids + (remove #(contains? fixed-mask-set %))) + frame-id)))) + fixed-include-ids + (when has-fixed? + (vec (conj (or fixed-mask-ids #{}) frame-id))) + fixed-clear-fills-ids (when has-fixed? #{frame-id})] + {:fixed-mask-set fixed-mask-set + :not-fixed-include-ids not-fixed-include-ids + :fixed-include-ids fixed-include-ids + :fixed-clear-fills-ids fixed-clear-fills-ids})) + +(mf/defc viewport-wasm* + {::mf/wrap [mf/memo]} + [{:keys [page frame base offset size delta is-fixed]}] + (let [delta (or delta (gpt/point 0 0)) + vbox (:vbox size) + is-fixed (true? is-fixed) + + fixed-layer-ref (mf/use-ref nil) + not-fixed-wasm-ref (mf/use-ref nil) + fixed-wasm-ref (mf/use-ref nil) + + objects (:objects page) + frame-id (:id frame) + scale (vpc/viewer-scale size) + page-id (:id page) + + frame (cond-> frame is-fixed (assoc :fixed-scroll true)) + objects (cond-> objects is-fixed (assoc-in [frame-id :fixed-scroll] true)) + + has-fixed? + (and (not is-fixed) + (some #(cfh/fixed-scroll? (get objects %)) + (cfh/get-children-ids objects frame-id))) + + prepared + (mf/with-memo [objects frame size delta] + (vpc/prepare-objects frame size delta objects)) + + prepared-all + (mf/with-memo [objects size delta] + (vpc/prepare-page-objects objects size delta)) + + {:keys [fixed-mask-set not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids]} + (mf/with-memo [objects frame-id has-fixed?] + (fixed-scroll-layer-ids objects frame-id has-fixed?)) + + prepared-frame (get prepared frame-id) + + not-fixed-props + (mf/props {:prepared prepared + :prepared-all prepared-all + :prepared-frame prepared-frame + :canvas-ref not-fixed-wasm-ref + :scale scale + :size size + :vbox vbox + :class (if has-fixed? + (stl/css :not-fixed) + (when is-fixed (stl/css :fixed))) + :shape-filter (when has-fixed? + #(not (contains? fixed-mask-set %)))}) + + fixed-props + (mf/props {:prepared prepared + :prepared-all prepared-all + :prepared-frame prepared-frame + :canvas-ref fixed-wasm-ref + :scale scale + :size size + :vbox vbox + :class (stl/css :not-fixed) + :shape-filter #(contains? fixed-mask-set %)})] + + (rwv/use-viewer-wasm-viewport! + page-id objects size scale frame-id + not-fixed-wasm-ref fixed-wasm-ref + (when has-fixed? fixed-layer-ref) + not-fixed-include-ids fixed-include-ids fixed-clear-fills-ids + delta) + + [:& (mf/provider shapes/base-frame-ctx) {:value (get prepared-all (:id base))} + [:& (mf/provider shapes/frame-offset-ctx) {:value offset} + [:* + [:> wasm-layer* not-fixed-props] + + (when has-fixed? + [:div {:ref fixed-layer-ref} + [:> wasm-layer* fixed-props]])]]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index d69d06a7d5..a304a2c27c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -193,6 +193,7 @@ [{:keys [state on-select-color on-add-library-color disable-gradient disable-opacity disable-image]}] (let [selected* (h/use-shared-state mdc/colorpicker-selected-broadcast-key :recent) selected (deref selected*) + layout (mf/deref refs/workspace-layout) view-mode* (mf/use-state :grid) view-mode (deref view-mode*) @@ -250,7 +251,7 @@ (r/set-resize-type! :bottom) (dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down") (st/emit! (dw/remove-layout-flag :textpalette) - (-> (mdc/show-palette selected) + (-> (mdc/toggle-palette selected) (vary-meta assoc ::ev/origin "workspace-colorpicker"))))) toggle-view-mode @@ -332,8 +333,9 @@ [:> icon-button* {:variant "ghost" - :aria-label (tr "workspace.libraries.colors.show-color-palette") + :aria-label (tr "workspace.libraries.colors.toggle-color-palette") :on-click toggle-palette + :aria-pressed (boolean (contains? layout :colorpalette)) :icon i/swatches}] [:> icon-button* diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 46f43b4512..0a2f5908f2 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -331,7 +331,7 @@ :class (stl/css :title-spacing-lib)}] [:div {:class (stl/css :section-list)} - [:div {:class (stl/css :section-list-item)} + [:div {:class (stl/css :section-list-publish)} [:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-title)} (tr "workspace.libraries.file-library")] [:ul {:class (stl/css :item-contents)} diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss index 79e0180d9d..01cbed12aa 100644 --- a/frontend/src/app/main/ui/workspace/libraries.scss +++ b/frontend/src/app/main/ui/workspace/libraries.scss @@ -93,12 +93,14 @@ border-radius: $br-8; } -.section-list-item { +.section-list-publish { @extend %section-list-item-placeholder; - &:first-child { - border: none; - } + border: none; +} + +.section-list-item { + @extend %section-list-item-placeholder; } .section-list-item-double-icon { diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index d4d0dd9f28..53d51a7970 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] - [app.common.time :as ct] [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.common :as dcm] @@ -30,6 +29,7 @@ [app.main.data.workspace.versions :as dwv] [app.main.features :as features] [app.main.refs :as refs] + [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.dropdown-menu :refer [dropdown-menu* dropdown-menu-item*]] @@ -790,20 +790,15 @@ [{:keys [on-close]}] (let [plugins? (features/active-feature? @st/state "plugins/runtime") - profile (mf/deref refs/profile) - mcp (mf/deref refs/mcp) - tokens (mf/deref refs/access-tokens) - - expires-at (some->> tokens - (some #(when (= (:type %) "mcp") %)) - :expires-at) - expired? (and (some? expires-at) (> (ct/now) expires-at)) + profile (mf/deref refs/profile) + mcp (mf/deref refs/mcp) + mcp-key-expired? (mf/deref refs/mcp-key-expired?) mcp-enabled? (true? (-> profile :props :mcp-enabled)) mcp-connection (get mcp :connection-status) mcp-connected? (= mcp-connection "connected") - show-enabled? (and mcp-enabled? (false? expired?)) + show-enabled? (and mcp-enabled? (false? mcp-key-expired?)) on-nav-to-integrations (mf/use-fn @@ -842,7 +837,7 @@ :pos-6 plugins?) :on-close on-close} - (when (and show-enabled? (not expired?)) + (when (and show-enabled? (not mcp-key-expired?)) [:> dropdown-menu-item* {:id "mcp-menu-toggle-mcp-plugin" :class (stl/css :base-menu-item :submenu-item) :on-click on-toggle-mcp-plugin @@ -864,7 +859,6 @@ (mf/defc menu* [{:keys [layout file]}] (let [profile (mf/deref refs/profile) - mcp (mf/deref refs/mcp) show-menu* (mf/use-state false) show-menu? (deref show-menu*) @@ -938,15 +932,29 @@ (fn [event] (dom/stop-propagation event) (let [renderer (or (-> profile :props :renderer) :svg) - next-renderer (if (= renderer :wasm) :svg :wasm)] - (st/emit! (ev/event {::ev/name (if (= next-renderer :wasm) - "enable-webgl-rendering" - "disable-webgl-rendering") - ::ev/origin "workspace:menu"}) - (du/update-profile-props {:renderer next-renderer}) - (ntf/success (tr (if (= next-renderer :wasm) - "webgl.toast.webgl-render-enabled" - "webgl.toast.webgl-render-disabled"))))))) + next-renderer (if (= renderer :wasm) :svg :wasm) + ev-name (if (= next-renderer :wasm) + "enable-webgl-rendering" + "disable-webgl-rendering")] + (if (cf/external-feature-flag "renderer-hard-reload" "test") + ;; Bare RPC + hard reload: skips `du/update-profile-props`, so + ;; `features/recompute-features` is not run here; bootstrap + ;; after reload resolves render-wasm/v1 from the saved profile. + (do + (st/emit! (ev/event {::ev/name ev-name + ::ev/origin "workspace:menu"})) + (->> (rp/cmd! :update-profile-props {:props {:renderer next-renderer}}) + (rx/subs! (fn [_] (dom/reload-current-window true)) + (fn [_] + (st/emit! (ntf/error (tr "errors.generic"))))))) + ;; `update-profile-props` WatchEvent calls + ;; `features/recompute-features`. + (st/emit! (ev/event {::ev/name ev-name + ::ev/origin "workspace:menu"}) + (du/update-profile-props {:renderer next-renderer}) + (ntf/success (tr (if (= next-renderer :wasm) + "webgl.toast.webgl-render-enabled" + "webgl.toast.webgl-render-disabled")))))))) open-plugins-manager (mf/use-fn @@ -1047,11 +1055,8 @@ :class (stl/css :item-arrow)}]]) (when (contains? cf/flags :mcp) - (let [tokens (mf/deref refs/access-tokens) - expires-at (some->> tokens - (some #(when (= (:type %) "mcp") %)) - :expires-at) - expired? (and (some? expires-at) (> (ct/now) expires-at)) + (let [mcp (mf/deref refs/mcp) + mcp-key-expired? (mf/deref refs/mcp-key-expired?) mcp-enabled? (true? (-> profile :props :mcp-enabled)) mcp-connection (get mcp :connection-status) @@ -1060,7 +1065,7 @@ active? (and mcp-enabled? mcp-connected?) failed? (or (and mcp-enabled? mcp-error?) - (true? expired?))] + (true? mcp-key-expired?))] [:> dropdown-menu-item* {:class (stl/css :base-menu-item :menu-item) :on-click on-menu-click diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index 7892f545d6..11ba417e9b 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -160,10 +160,11 @@ width (obj/get dom "clientWidth")] (swap! state* assoc :width width))) - [:div {:class (stl/css :palette-wrapper) - :id "palette-wrapper" - :style (calculate-palette-style rulers?) - :data-testid "palette"} + [:section {:class (stl/css :palette-wrapper) + :id "palette-wrapper" + :style (calculate-palette-style rulers?) + :aria-label (tr "workspace.toolbar.palette-bar") + :data-testid "palette"} (when-not ^boolean read-only? [:div {:ref parent-ref :class (dm/str size-classname " " (stl/css-case :palettes true diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs index d4a51bac77..749c5af69f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs @@ -17,7 +17,6 @@ [app.render-wasm.api :as wasm.api] [app.render-wasm.text-editor :as text-editor] [app.util.dom :as dom] - [app.util.object :as obj] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -40,12 +39,10 @@ (if (>= (count lang) 3) (str/capital lang) (str/upper lang))) "Noto Color Emoji")) -(mf/defc text-editor +(mf/defc text-editor* "Contenteditable element positioned over the text shape to capture input events." - {::mf/wrap-props false} - [props] - (let [shape (obj/get props "shape") - shape-id (dm/get-prop shape :id) + [{:keys [shape]}] + (let [shape-id (dm/get-prop shape :id) clip-id (dm/str "text-edition-clip" shape-id) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs index 643b4861a6..43627008bd 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs @@ -23,7 +23,7 @@ [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.assets.groups :as grp] - [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry]] + [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry*]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [cuerdas.core :as str] @@ -112,17 +112,17 @@ :on-drag-over dom/prevent-default :on-drop on-drop} - [:& typography-entry + [:> typography-entry* {:file-id file-id :typography typography - :local? local? - :selected? (contains? selected typography-id) + :is-local local? + :is-selected (contains? selected typography-id) :on-click on-asset-click :on-change handle-change :on-context-menu on-context-menu - :editing? editing? - :renaming? renaming? - :focus-name? rename? + :is-editing editing? + :is-renaming renaming? + :is-focus-name rename? :external-open* open* :is-asset? true}] (when ^boolean dragging? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 186edf7978..1eb144494f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -30,7 +30,7 @@ [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.token-typography-row :refer [token-typography-row*]] - [app.main.ui.workspace.sidebar.options.menus.typography :refer [text-options* typography-entry]] + [app.main.ui.workspace.sidebar.options.menus.typography :refer [text-options* typography-entry*]] [app.main.ui.workspace.tokens.management.forms.controls.utils :as csu] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -516,11 +516,11 @@ :icon i/detach}]] typography - [:& typography-entry {:file-id typography-file-id - :typography typography - :local? (= typography-file-id file-id) - :on-detach handle-detach-typography - :on-change handle-change-typography}] + [:> typography-entry* {:file-id typography-file-id + :typography typography + :is-local (= typography-file-id file-id) + :on-detach handle-detach-typography + :on-change handle-change-typography}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 00bccb7c5f..38c15568dd 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -230,8 +230,7 @@ :on-click on-select :is-current (= (:id font) (:id selected))}]))) -(mf/defc font-options - {::mf/wrap-props false} +(mf/defc font-options* [{:keys [values on-change on-blur show-recent full-size-selector]}] (let [{:keys [font-id font-size font-variant-id]} values @@ -375,8 +374,7 @@ :on-change on-font-variant-change :on-blur on-blur}])]]])) -(mf/defc spacing-options - {::mf/wrap-props false} +(mf/defc spacing-options* [{:keys [values on-change on-blur]}] (let [{:keys [line-height letter-spacing]} values @@ -384,7 +382,7 @@ letter-spacing (or letter-spacing "0") handle-change (fn [value attr] - (on-change {attr (str value)}))] + (on-change {attr (ust/format-precision value 2)}))] [:div {:class (stl/css :spacing-options)} [:div {:class (stl/css :line-height) @@ -424,8 +422,7 @@ :nillable (= :multiple letter-spacing) :on-blur on-blur}]]])) -(mf/defc text-transform-options - {::mf/wrap-props false} +(mf/defc text-transform-options* [{:keys [values on-change on-blur]}] (let [text-transform (or (:text-transform values) "none") unset-value (if (features/active-feature? @st/state "text-editor/v2") "none" "unset") @@ -459,28 +456,29 @@ (mf/defc text-options* [{:keys [ids editor values on-change on-blur show-recent]}] (let [full-size-selector? (and show-recent (= (mf/use-ctx ctx/sidebar) :right)) - opts #js {:editor editor - :ids ids - :values values - :on-change on-change - :on-blur on-blur - :show-recent show-recent - :full-size-selector full-size-selector?}] + opts (mf/props + {:editor editor + :ids ids + :values values + :on-change on-change + :on-blur on-blur + :show-recent show-recent + :full-size-selector full-size-selector?})] [:div {:class (stl/css-case :text-options true :text-options-full-size full-size-selector?)} - [:> font-options opts] + [:> font-options* opts] [:div {:class (stl/css :typography-variations)} - [:> spacing-options opts] - [:> text-transform-options opts]]])) + [:> spacing-options* opts] + [:> text-transform-options* opts]]])) -(mf/defc typography-advanced-options +(mf/defc typography-advanced-options* {::mf/wrap [mf/memo]} - [{:keys [visible? typography editable? name-input-ref on-close on-change on-name-blur - local? navigate-to-library on-key-down file-id is-asset?]}] + [{:keys [is-visible typography is-editable name-input-ref on-close on-change on-name-blur + is-local navigate-to-library on-key-down file-id is-asset?]}] (let [ref (mf/use-ref nil) font-data (fonts/get-font-data (:font-id typography)) typography-id (:id typography) - show-actions? (and is-asset? editable?) + show-actions? (and is-asset? is-editable) on-delete (mf/use-fn @@ -501,17 +499,17 @@ (fonts/ensure-loaded! (:font-id typography)) (mf/use-effect - (mf/deps visible?) + (mf/deps is-visible) (fn [] (when-let [node (mf/ref-val ref)] - (when visible? + (when is-visible (dom/scroll-into-view-if-needed! node))))) - (when visible? + (when is-visible [:div {:ref ref :class (stl/css :advanced-options-wrapper)} - (if ^boolean editable? + (if ^boolean is-editable [:* [:div {:class (stl/css :font-name-wrapper)} [:div {:class (stl/css :typography-sample-input) @@ -588,19 +586,18 @@ [:span {:class (stl/css :info-label)} (tr "workspace.assets.typography.text-transform")] [:span {:class (stl/css :info-content)} (:text-transform typography)]] - (when-not local? + (when-not is-local [:> button* {:variant "secondary" :on-click navigate-to-library} (tr "workspace.assets.typography.go-to-edit")])])]))) -(mf/defc typography-entry - {::mf/wrap-props false} - [{:keys [file-id typography local? selected? on-click on-change on-detach on-context-menu editing? renaming? focus-name? external-open* is-asset?]}] +(mf/defc typography-entry* + [{:keys [file-id typography is-local is-selected on-click on-change on-detach on-context-menu is-editing is-renaming is-focus-name external-open* is-asset?]}] (let [name-input-ref (mf/use-ref) read-only? (mf/use-ctx ctx/workspace-read-only?) - editable? (and local? (not read-only?)) + editable? (and is-local (not read-only?)) - open* (mf/use-state editing?) + open* (mf/use-state is-editing) open? (deref open*) font-data (fonts/get-font-data (:font-id typography)) name-only? (= (:name typography) (:name font-data)) @@ -638,16 +635,16 @@ (when ^boolean esc? (dom/blur! input-node)))))] - (mf/with-effect [editing?] - (when editing? - (reset! open* editing?))) + (mf/with-effect [is-editing] + (when is-editing + (reset! open* is-editing))) (mf/with-effect [open?] (when (some? external-open*) (reset! external-open* open?))) - (mf/with-effect [focus-name?] - (when focus-name? + (mf/with-effect [is-focus-name] + (when is-focus-name (tm/schedule #(when-let [node (mf/ref-val name-input-ref)] (dom/focus! node) @@ -655,9 +652,9 @@ [:* [:div {:class (stl/css-case :typography-entry true - :selected ^boolean selected?) + :selected ^boolean is-selected) :style {:display (when ^boolean open? "none")}} - (if renaming? + (if is-renaming [:div {:class (stl/css :font-name-wrapper)} [:div {:class (stl/css :typography-sample-input) @@ -706,16 +703,16 @@ :on-click on-open :icon i/menu}]]] - [:& typography-advanced-options - {:visible? open? + [:> typography-advanced-options* + {:is-visible open? :on-close on-close :typography typography - :editable? editable? + :is-editable editable? :name-input-ref name-input-ref :on-change on-change :on-name-blur on-name-blur :on-key-down on-key-down :file-id file-id :is-asset? is-asset? - :local? local? + :is-local is-local :navigate-to-library navigate-to-library}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index ce00c06d60..3283050d23 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -88,7 +88,7 @@ (p/resolved nil) ;; Blur with Skia, then capture the already-blurred frame. (do (wasm.api/render-blurred-snapshot!) - (wasm.api/capture-canvas-snapshot-url))) + (wasm.api/capture-canvas-snapshot))) (p/finally (fn [] (wasm.api/apply-canvas-blur) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/themes.cljs index 37c5b0e0be..ce9a960b5f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes.cljs @@ -12,7 +12,7 @@ [app.main.ui.context :as ctx] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.foundations.typography.text :refer [text*]] - [app.main.ui.workspace.tokens.themes.theme-selector :refer [theme-selector]] + [app.main.ui.workspace.tokens.themes.theme-selector :refer [theme-selector*]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) @@ -44,7 +44,7 @@ (tr "workspace.tokens.create-one")])] (if can-edit? [:div {:class (stl/css :theme-selector-wrapper)} - [:& theme-selector] + [:> theme-selector*] [:> button* {:variant "secondary" :type "button" :class (stl/css :edit-theme-button) @@ -52,4 +52,4 @@ (tr "labels.edit")]] [:div {:title (when-not can-edit? (tr "workspace.tokens.no-permission-themes"))} - [:& theme-selector]]))])) + [:> theme-selector*]]))])) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs index 35a9005521..92f5f6ad03 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs @@ -23,8 +23,8 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(mf/defc themes-list - [{:keys [themes active-theme-paths on-close grouped?]}] +(mf/defc themes-list* + [{:keys [themes active-theme-paths on-close is-grouped]}] (when (seq themes) [:ul {:class (stl/css :theme-options)} (for [[_ {:keys [id name] :as theme}] themes @@ -39,7 +39,7 @@ :aria-selected selected? :class (stl/css-case :checked-element true - :sub-item grouped? + :sub-item is-grouped :is-selected selected?) :on-click select-theme} [:> text* {:as "span" :typography "body-small" :class (stl/css :label) :title name} name] @@ -52,7 +52,7 @@ [] (modal/show! :tokens/themes {})) -(mf/defc theme-options +(mf/defc theme-options* [{:keys [active-theme-paths themes on-close]}] [:ul {:class (stl/css :theme-options :custom-select-dropdown) :role "listbox"} @@ -62,10 +62,10 @@ :role "group"} (when (seq group) [:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group]) - [:& themes-list {:themes themes - :active-theme-paths active-theme-paths - :on-close on-close - :grouped? true}]]) + [:> themes-list* {:themes themes + :active-theme-paths active-theme-paths + :on-close on-close + :is-grouped true}]]) [:li {:class (stl/css :separator) :aria-hidden true}] [:li {:class (stl/css-case :checked-element true @@ -75,7 +75,7 @@ [:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")] [:> icon* {:icon-id i/arrow-right :aria-hidden true}]]]) -(mf/defc theme-selector +(mf/defc theme-selector* [{:keys []}] (let [;; Store active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden) @@ -140,7 +140,7 @@ [:& dropdown {:show is-open? :on-close on-close-dropdown} - [:& theme-options {:active-theme-paths active-theme-paths - :themes themes - :on-close on-close-dropdown}]]]) + [:> theme-options* {:active-theme-paths active-theme-paths + :themes themes + :on-close on-close-dropdown}]]]) container))])) diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs index 53dbf65e47..eb9675beae 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs @@ -8,15 +8,18 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.geom.point :as gpt] + [app.config :as cf] [app.main.data.event :as ev] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.mcp :as mcp] [app.main.data.workspace.media :as dwm] [app.main.data.workspace.shortcuts :as sc] [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.components.dropdown-menu :refer [dropdown-menu* dropdown-menu-item*]] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.context :as ctx] [app.main.ui.icons :as deprecated-icon] @@ -26,6 +29,61 @@ [okulary.core :as l] [rumext.v2 :as mf])) +(mf/defc mcp-indicator* + [] + (let [profile (mf/deref refs/profile) + mcp (mf/deref refs/mcp) + mcp-key-expired? (mf/deref refs/mcp-key-expired?) + + mcp-enabled? (true? (-> profile :props :mcp-enabled)) + mcp-connected? (= "connected" (:connection-status mcp)) + show-indicator? (and mcp-enabled? (false? mcp-key-expired?)) + + mcp-menu-open* (mf/use-state false) + mcp-menu-open? (deref mcp-menu-open*) + + toggle-mcp-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (swap! mcp-menu-open* not))) + + close-mcp-menu + (mf/use-fn + #(reset! mcp-menu-open* false)) + + connect-mcp + (mf/use-fn + #(st/emit! (mcp/connect-mcp) + (ev/event {::ev/name "connect-mcp-plugin" + ::ev/origin "workspace:toolbar"})))] + (when show-indicator? + [:li + [:button + {:title (tr "workspace.toolbar.mcp") + :aria-label (tr "workspace.toolbar.mcp") + :class (stl/css-case :main-toolbar-options-button true + :mcp-button true + :selected mcp-menu-open?) + :on-click toggle-mcp-menu + :data-tool "mcp" + :data-testid "mcp-btn"} + [:span {:class (stl/css-case :mcp-status-dot true + :connected mcp-connected?)}] + [:span {:class (stl/css-case :mcp-button-label true + :connected mcp-connected?)} + (tr "workspace.toolbar.mcp")]] + [:> dropdown-menu* {:show mcp-menu-open? + :on-close close-mcp-menu + :class (stl/css :mcp-menu)} + (if mcp-connected? + [:li {:class (stl/css :mcp-menu-info) + :role "presentation"} + (tr "workspace.toolbar.mcp-connected")] + [:> dropdown-menu-item* {:class (stl/css :mcp-menu-item) + :on-click connect-mcp} + (tr "workspace.toolbar.mcp-connect-here")])]]))) + (mf/defc image-upload* {::mf/wrap [mf/memo]} [] @@ -221,12 +279,13 @@ {:title "Debugging tool" :class (stl/css-case :main-toolbar-options-button true :selected (contains? layout :debug-panel)) :on-click toggle-debug-panel} - deprecated-icon/bug]])]] + deprecated-icon/bug]]) + + (when (contains? cf/flags :mcp) + [:> mcp-indicator*])]] [:button {:title (tr "workspace.toolbar.toggle-toolbar") :aria-label (tr "workspace.toolbar.toggle-toolbar") :class (stl/css :toolbar-handler) :on-click toggle-toolbar} [:div {:class (stl/css :toolbar-handler-btn)}]]]))) - - diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.scss b/frontend/src/app/main/ui/workspace/top_toolbar.scss index b67c925988..7a185eb692 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.scss +++ b/frontend/src/app/main/ui/workspace/top_toolbar.scss @@ -5,6 +5,9 @@ // Copyright (c) KALEIDOS INC Sucursal en España SL @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_utils.scss" as *; +@use "ds/typography.scss" as t; .main-toolbar { cursor: initial; @@ -37,7 +40,7 @@ } .main-toolbar-hidden { - --toolbar-offset-y: -#{deprecated.$s-4}; + --toolbar-offset-y: calc(-1 * #{deprecated.$s-4}); height: deprecated.$s-16; z-index: deprecated.$z-index-1; @@ -83,6 +86,102 @@ } } +.mcp-button { + display: flex; + align-items: center; + gap: var(--sp-xs); + width: fit-content; + margin-inline-start: var(--sp-xs); + padding-inline: var(--sp-s); +} + +.mcp-button-label { + @include t.use-typography("body-small"); + + &.connected { + color: var(--color-accent-primary); + } +} + +.mcp-status-dot { + // Connection indicator placed before the label, vertically centered: + // a muted gray when disconnected, the primary accent when connected. + position: relative; + flex-shrink: 0; + inline-size: px2rem(6); + block-size: px2rem(6); + border-radius: 50%; + background-color: var(--color-background-disabled); + + &.connected { + background-color: var(--color-accent-primary); + } + + // One-shot "blob" ripple that confirms the moment the tab becomes + // connected (e.g. after using "Switch here"). Triggered purely by the + // `.connected` class appearing, in sync with the color change. + &.connected::after { + content: ""; + position: absolute; + inset: 0; + border-radius: 50%; + background-color: var(--color-accent-primary); + opacity: 0; + animation: mcp-status-blob 0.5s ease-out; + } +} + +@keyframes mcp-status-blob { + from { + opacity: 0.5; + transform: scale(1); + } + + to { + opacity: 0; + transform: scale(2.5); + } +} + +.mcp-menu { + @include deprecated.menu-shadow; + + position: absolute; + inset-block-start: calc(100% + var(--sp-xs)); + inset-inline-start: var(--sp-xs); + z-index: var(--z-index-dropdown); + margin: 0; + padding: var(--sp-xs); + list-style: none; + border: $b-1 solid var(--panel-border-color); + border-radius: $br-8; + background-color: var(--menu-background-color); +} + +// Non-interactive informational text inside the menu (no hover, not focusable). +.mcp-menu-info { + @include t.use-typography("body-small"); + + padding: var(--sp-s) var(--sp-m); + color: var(--color-foreground-secondary); + white-space: nowrap; +} + +.mcp-menu-item { + @include t.use-typography("body-small"); + + display: flex; + align-items: center; + padding: var(--sp-s) var(--sp-m); + border-radius: $br-8; + color: var(--menu-foreground-color); + white-space: nowrap; + + &:hover { + background-color: var(--menu-background-color-hover); + } +} + .toolbar-handler { @include deprecated.flex-center; @include deprecated.button-style; diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index e842451c2f..3c5fb33114 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -537,33 +537,33 @@ :bounds vbox}]])) (when show-padding? - [:& mfc/padding-control + [:> mfc/padding-control* {:frame first-shape :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift? + :is-alt @alt? + :is-shift @shift? :on-move-selected on-move-selected :on-context-menu on-menu-selected}]) (when show-padding? - [:& mfc/gap-control + [:> mfc/gap-control* {:frame first-shape :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift? + :is-alt @alt? + :is-shift @shift? :on-move-selected on-move-selected :on-context-menu on-menu-selected}]) (when show-margin? - [:& mfc/margin-control + [:> mfc/margin-control* {:shape first-shape :parent selected-frame :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift?}]) + :is-alt @alt? + :is-shift @shift?}]) [:> widgets/frame-titles* {:objects base-objects diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 134f610c1c..d62ca3cf6b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -23,6 +23,8 @@ [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] [app.main.ui.workspace.viewport.rulers :as rulers] + [app.main.ui.workspace.viewport.viewport-ref :as uwvv] + [app.render-wasm.api :as wasm.api] [app.util.dom :as dom] [app.util.keyboard :as kbd] [cuerdas.core :as str] @@ -38,11 +40,51 @@ (def ^:const guide-pill-corner-radius 4) (def ^:const guide-active-area 16) +;; Manhattan distance (in screen pixels) the pointer must travel after +;; pointerdown before we consider the interaction a drag. Below this we keep +;; the hover state so a click — including the clicks that make up a +;; double-click — doesn't flicker the overlay pill. +(def ^:const guide-drag-threshold 3) + (def ^:const guide-creation-margin-left 8) (def ^:const guide-creation-margin-top 28) (def ^:const guide-creation-width 16) (def ^:const guide-creation-height 24) +(defn compute-guide-drag-position + "Computes the guide axis position from pointer drag delta." + [{:keys [axis position start-pos start-pt current-pt zoom snap-pixel?]}] + (let [delta (/ (- (get current-pt axis) (get start-pt axis)) zoom) + new-position (if (some? position) + (+ position delta) + (+ start-pos delta))] + (if snap-pixel? + (mth/round new-position) + new-position))) + +(defn guide-visible-in-focus? + "When focus mode is active, only free guides and guides bound to a focused + board are visible and interactive." + [focus frame-id] + (or (nil? frame-id) + (empty? focus) + (contains? focus frame-id))) + +(defn wasm-visible-guides + "Guide map sent to the WASM renderer. Must be the same map used to resolve + `find-guide-at` indices (`guide-by-serialized-index`). Filters by + rulers/grids visibility, focus mode, and excludes the guide currently being + dragged (the SVG overlay draws it instead)." + [{:keys [guides visible? focused dragging-id]}] + (let [guides (if visible? (or guides {}) {}) + guides (if (seq focused) + (into {} (filter (fn [[_ guide]] + (guide-visible-in-focus? focused (:frame-id guide))) + guides)) + guides)] + (cond-> guides + dragging-id (dissoc dragging-id)))) + (defn use-guide "Hooks to support drag/drop for existing guides and new guides" [on-guide-change get-hover-frame zoom {:keys [id position axis frame-id]}] @@ -120,21 +162,21 @@ on-pointer-move (mf/use-fn - (mf/deps position zoom snap-pixel? read-only? get-hover-frame) + (mf/deps position zoom snap-pixel? read-only? get-hover-frame axis) (fn [event] (when-not read-only? (when (mf/ref-val dragging-ref) (let [start-pt (mf/ref-val start-ref) start-pos (mf/ref-val start-pos-ref) current-pt (dom/get-client-position event) - delta (/ (- (get current-pt axis) (get start-pt axis)) zoom) - new-position (if (some? position) - (+ position delta) - (+ start-pos delta)) - new-position (if snap-pixel? - (mth/round new-position) - new-position) - + new-position (compute-guide-drag-position + {:axis axis + :position position + :start-pos start-pos + :start-pt start-pt + :current-pt current-pt + :zoom zoom + :snap-pixel? snap-pixel?}) new-frame-id (-> (get-hover-frame) (get :id))] @@ -283,6 +325,125 @@ (and (>= (:position guide) (:y frame)) (<= (:position guide) (+ (:y frame) (:height frame)))))) +(mf/defc guide-pill* + "Presentational pill shown next to a guide line: a colored rounded rect with + either the guide position as text or, when `editing`, an inline number input. + Shared by the SVG (`guide*`) and WASM overlay (`guide-overlay*`) renderers; + each owns its own interaction model and passes the relevant handlers in." + {::mf/wrap [mf/memo]} + [{:keys [pos vbox zoom axis color frame-offset editing + input-ref on-input-key-down on-input-blur on-double-click]}] + (let [{:keys [rect-x rect-y rect-width rect-height text-x text-y]} + (guide-pill-axis pos vbox zoom axis) + corner-radius (/ guide-pill-corner-radius zoom) + display-value (fmt/format-number (- pos frame-offset)) + input-w (/ guide-pill-width zoom) + input-h (/ guide-pill-height zoom)] + [:g.guide-pill + [:rect {:x rect-x + :y rect-y + :width rect-width + :height rect-height + :rx corner-radius + :ry corner-radius + :style {:fill color} + :on-double-click on-double-click}] + + (if editing + [:foreignObject {:x (- text-x (/ input-w 2)) + :y (- text-y (/ input-h 2)) + :width input-w + :height input-h + :transform (when (= axis :y) + (str "rotate(-90 " text-x "," text-y ")"))} + [:input {:ref input-ref + :type "number" + :step "any" + :default-value display-value + :auto-focus true + :on-key-down on-input-key-down + :on-blur on-input-blur + :on-pointer-down dom/stop-propagation + :style {:width "100%" + :height "100%" + :border "none" + :outline "none" + :padding 0 + :margin 0 + :background "transparent" + :color colors/white + :font-family rulers/font-family + :font-size (str (/ rulers/font-size zoom) "px") + :text-align "center" + :-moz-appearance "textfield"}}]] + [:text {:x text-x + :y text-y + :text-anchor "middle" + :dominant-baseline "middle" + :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) + :style {:font-size (/ rulers/font-size zoom) + :font-family rulers/font-family + :fill colors/white + :pointer-events "none"}} + display-value])])) + +(mf/defc guide-line* + "Presentational guide line. With a `frame`, draws the solid in-frame segment + and, on `hover?`, the dotted out-of-frame extensions; without a frame, a + single solid line. `show-main?` (default true) lets callers suppress the solid + segment when the render engine already draws it — e.g. the WASM hover overlay, + which only needs the dotted extensions on top of the WASM-rendered line." + {::mf/wrap [mf/memo]} + [{:keys [pos vbox zoom axis color frame hover? show-main?] + :or {show-main? true}}] + (let [width (/ guide-width zoom) + main-opacity (if hover? guide-opacity-hover guide-opacity) + dash (str "0, " (/ 6 zoom))] + (if (some? frame) + (let [{:keys [l1-x1 l1-y1 l1-x2 l1-y2 + l2-x1 l2-y1 l2-x2 l2-y2 + l3-x1 l3-y1 l3-x2 l3-y2]} + (guide-line-axis pos vbox frame axis)] + [:g + (when hover? + [:line {:x1 l1-x1 + :y1 l1-y1 + :x2 l1-x2 + :y2 l1-y2 + :style {:stroke color + :stroke-opacity guide-opacity-hover + :stroke-dasharray dash + :stroke-linecap "round" + :stroke-width width}}]) + (when show-main? + [:line {:x1 l2-x1 + :y1 l2-y1 + :x2 l2-x2 + :y2 l2-y2 + :style {:stroke color + :stroke-width width + :stroke-opacity main-opacity}}]) + (when hover? + [:line {:x1 l3-x1 + :y1 l3-y1 + :x2 l3-x2 + :y2 l3-y2 + :style {:stroke color + :stroke-opacity guide-opacity-hover + :stroke-width width + :stroke-dasharray dash + :stroke-linecap "round"}}])]) + + (when show-main? + (let [{:keys [x1 y1 x2 y2]} (guide-line-axis pos vbox axis)] + [:line {:x1 x1 + :y1 y1 + :x2 x2 + :y2 y2 + :style {:stroke color + :stroke-width width + :stroke-opacity main-opacity}}]))))) + (mf/defc guide* {::mf/wrap [mf/memo]} [{:keys [guide is-hover on-guide-change get-hover-frame vbox zoom @@ -341,12 +502,6 @@ pos (+ (or (:new-position @state) (:position guide)) (get move-vec axis)) - guide-width - (/ guide-width zoom) - - guide-pill-corner-radius - (/ guide-pill-corner-radius zoom) - frame-guide-outside? (and (some? frame) (not (is-guide-inside-frame? (assoc guide :position pos) frame))) @@ -427,106 +582,28 @@ :on-context-menu on-context-menu :on-double-click on-double-click}])) - (if (some? frame) - (let [{:keys [l1-x1 l1-y1 l1-x2 l1-y2 - l2-x1 l2-y1 l2-x2 l2-y2 - l3-x1 l3-y1 l3-x2 l3-y2]} - (guide-line-axis pos vbox frame axis)] - [:g - (when (or is-hover (:hover @state)) - [:line {:x1 l1-x1 - :y1 l1-y1 - :x2 l1-x2 - :y2 l1-y2 - :style {:stroke guide-color - :stroke-opacity guide-opacity-hover - :stroke-dasharray (str "0, " (/ 6 zoom)) - :stroke-linecap "round" - :stroke-width guide-width}}]) - [:line {:x1 l2-x1 - :y1 l2-y1 - :x2 l2-x2 - :y2 l2-y2 - :style {:stroke guide-color - :stroke-width guide-width - :stroke-opacity (if (or is-hover (:hover @state)) - guide-opacity-hover - guide-opacity)}}] - (when (or is-hover (:hover @state)) - [:line {:x1 l3-x1 - :y1 l3-y1 - :x2 l3-x2 - :y2 l3-y2 - :style {:stroke guide-color - :stroke-opacity guide-opacity-hover - :stroke-width guide-width - :stroke-dasharray (str "0, " (/ 6 zoom)) - :stroke-linecap "round"}}])]) - - (let [{:keys [x1 y1 x2 y2]} (guide-line-axis pos vbox axis)] - [:line {:x1 x1 - :y1 y1 - :x2 x2 - :y2 y2 - :style {:stroke guide-color - :stroke-width guide-width - :stroke-opacity (if (or is-hover (:hover @state)) - guide-opacity-hover - guide-opacity)}}])) + [:> guide-line* {:pos pos + :vbox vbox + :zoom zoom + :axis axis + :color guide-color + :frame frame + :hover? (or is-hover (:hover @state))}] + ;; If the guide is associated to a frame we show the position relative + ;; to the frame (handled via `frame-offset` inside `guide-pill*`). (when (or is-hover (:hover @state) is-editing) - (let [{:keys [rect-x rect-y rect-width rect-height text-x text-y]} - (guide-pill-axis pos vbox zoom axis) - display-value (fmt/format-number (- pos frame-offset)) - input-w (/ guide-pill-width zoom) - input-h (/ guide-pill-height zoom)] - [:g.guide-pill - [:rect {:x rect-x - :y rect-y - :width rect-width - :height rect-height - :rx guide-pill-corner-radius - :ry guide-pill-corner-radius - :style {:fill guide-color} - :on-double-click on-double-click}] - - (if is-editing - [:foreignObject {:x (- text-x (/ input-w 2)) - :y (- text-y (/ input-h 2)) - :width input-w - :height input-h - :transform (when (= axis :y) - (str "rotate(-90 " text-x "," text-y ")"))} - [:input {:ref input-ref - :type "number" - :step "any" - :default-value display-value - :auto-focus true - :on-key-down on-input-key-down - :on-blur accept-editing - :on-pointer-down dom/stop-propagation - :style {:width "100%" - :height "100%" - :border "none" - :outline "none" - :padding 0 - :margin 0 - :background "transparent" - :color colors/white - :font-family rulers/font-family - :font-size (str (/ rulers/font-size zoom) "px") - :text-align "center" - :-moz-appearance "textfield"}}]] - [:text {:x text-x - :y text-y - :text-anchor "middle" - :dominant-baseline "middle" - :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) - :style {:font-size (/ rulers/font-size zoom) - :font-family rulers/font-family - :fill colors/white}} - ;; If the guide is associated to a frame we show the position relative to the frame - display-value])]))]))) + [:> guide-pill* {:pos pos + :vbox vbox + :zoom zoom + :axis axis + :color guide-color + :frame-offset frame-offset + :editing is-editing + :input-ref input-ref + :on-input-key-down on-input-key-down + :on-input-blur accept-editing + :on-double-click on-double-click}])]))) (mf/defc new-guide-area* [{:keys [vbox zoom axis get-hover-frame disabled-guides]}] @@ -581,10 +658,407 @@ :is-hover true :hover-frame frame}])])) +(defn- guide-by-serialized-index + "Maps a WASM guide index back to the guide map entry. `guides` must be the + same map passed to `set-guides` (typically `wasm-visible-guides`); index + order matches `write-guides` / `(vec (vals guides))`." + [guides index] + (when (>= index 0) + (nth (vec (vals guides)) index nil))) + +(mf/defc guide-overlay* + "Temporary SVG rendering of a guide that's being interacted with (drag, hover + or inline edit). In :hover mode the WASM engine still draws the (in-frame) + line, so we only overlay the position pill plus, for frame-anchored guides, + the dotted out-of-frame extensions. Drag and edit hide the WASM line and draw + the full SVG line. In :edit mode the pill is an editable input." + [{:keys [guide position vbox zoom mode frame frame-offset + on-input-commit on-input-cancel]}] + (let [axis (:axis guide) + guide-color (or (:color guide) default-guide-color) + input-ref (mf/use-ref nil) + ;; In :hover mode the WASM engine still renders the guide line, so we + ;; only overlay the dotted extensions. Drag and edit hide the WASM line + ;; and require the full SVG line. + show-line? (not= mode :hover) + show-pill? (or (= mode :edit) (= mode :hover)) + editing? (= mode :edit) + + on-key-down + (mf/use-fn + (mf/deps on-input-commit on-input-cancel) + (fn [event] + (cond + (kbd/enter? event) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (on-input-commit (-> (mf/ref-val input-ref) dom/get-value))) + + (kbd/esc? event) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (on-input-cancel))))) + + on-blur + (mf/use-fn + (mf/deps on-input-commit) + (fn [] + (on-input-commit (-> (mf/ref-val input-ref) dom/get-value))))] + + (mf/with-effect [mode] + (when editing? + (some-> (mf/ref-val input-ref) dom/select-text!))) + + [:g.guide-overlay + ;; Drag/edit: WASM hides its line, so draw the full SVG guide line. + (when show-line? + [:> guide-line* {:pos position + :vbox vbox + :zoom zoom + :axis axis + :color guide-color + :hover? true}]) + + ;; Hover: WASM still draws the in-frame segment; only add the dotted + ;; out-of-frame extensions for frame-anchored guides. + (when (and (= mode :hover) (some? frame)) + [:> guide-line* {:pos position + :vbox vbox + :zoom zoom + :axis axis + :color guide-color + :frame frame + :hover? true + :show-main? false}]) + + (when show-pill? + [:> guide-pill* {:pos position + :vbox vbox + :zoom zoom + :axis axis + :color guide-color + :frame-offset frame-offset + :editing editing? + :input-ref input-ref + :on-input-key-down on-key-down + :on-input-blur on-blur}])])) + +(defn use-wasm-guide-interaction + "Owns both drag and inline-edit lifecycles for WASM-rendered guides. + + Returns a map with the live overlay `state` (`{:guide ... :new-position ... + :mode :drag|:edit ...}` or nil) plus callbacks the overlay needs in edit + mode: `commit-edit` (commits the parsed input value) and `cancel-edit` + (drops the edit without committing)." + [{:keys [wasm-guides zoom wasm-guides? disabled-guides? on-guide-change + on-guide-drag on-guide-hover get-hover-frame focus]}] + (let [dragging-ref (mf/use-ref false) + moved-ref (mf/use-ref false) + start-ref (mf/use-ref nil) + guide-ref (mf/use-ref nil) + pending-ref (mf/use-ref nil) + drag-listeners-ref (mf/use-ref nil) + hover-axis-ref (mf/use-ref nil) + hover-guide-id-ref (mf/use-ref nil) + state (mf/use-state nil) + + snap-pixel? + (mf/deref refs/snap-pixel?) + + read-only? + (mf/use-ctx ctx/workspace-read-only?) + + ;; The handlers are defined here so they close directly over the refs and + ;; the current render's props (guides, zoom, ...). The pointerdown / + ;; dblclick listeners are re-registered by the effect below whenever those + ;; props change, so they always see fresh values. + remove-drag-listeners + (fn [] + (when-let [{:keys [on-move on-up]} (mf/ref-val drag-listeners-ref)] + (when-let [viewport @uwvv/viewport-ref] + (.removeEventListener viewport "pointermove" on-move true) + (.removeEventListener viewport "pointerup" on-up true) + (.removeEventListener viewport "pointercancel" on-up true)) + (mf/set-ref-val! drag-listeners-ref nil))) + + emit-hover-axis + (fn [axis] + (when (not= axis (mf/ref-val hover-axis-ref)) + (mf/set-ref-val! hover-axis-ref axis) + (when (some? on-guide-hover) + (on-guide-hover axis)))) + + ;; Mirrors what the SVG renderer does on pointer-enter / -leave: + ;; populates `[:workspace-guides :hover]` so the Del / Backspace + ;; shortcut (`dw/delete-selected`) can remove the hovered guide. + emit-hover-guide-id + (fn [id] + (let [prev (mf/ref-val hover-guide-id-ref)] + (when (not= id prev) + (mf/set-ref-val! hover-guide-id-ref id) + (when prev + (st/emit! (dw/set-hover-guide prev false))) + (when id + (st/emit! (dw/set-hover-guide id true)))))) + + clear-drag-refs + (fn [] + (remove-drag-listeners) + (mf/set-ref-val! dragging-ref false) + (mf/set-ref-val! moved-ref false) + (mf/set-ref-val! start-ref nil) + (mf/set-ref-val! guide-ref nil) + (mf/set-ref-val! pending-ref nil)) + + reset-state + (fn [] + (clear-drag-refs) + (when (some? on-guide-drag) + (on-guide-drag nil)) + (emit-hover-axis nil) + (emit-hover-guide-id nil) + (reset! state nil)) + + finish-drag + (fn [event] + (when (mf/ref-val dragging-ref) + (let [moved? (mf/ref-val moved-ref)] + (when (and moved? (some? on-guide-change)) + (when-let [{:keys [guide new-position new-frame-id]} + (mf/ref-val pending-ref)] + (when (and (some? guide) (some? new-position)) + (on-guide-change (assoc guide + :position new-position + :frame-id new-frame-id))))) + (when-let [viewport @uwvv/viewport-ref] + (when (.-pointerId event) + (.releasePointerCapture viewport (.-pointerId event)))) + ;; A click without movement (no drag): leave the hover state + ;; alone so a follow-up double-click can transition straight + ;; from :hover to :edit without flickering through nil. + (if moved? + (reset-state) + (clear-drag-refs))))) + + drag-move + (fn [move-event] + (when (mf/ref-val dragging-ref) + (when-let [guide (mf/ref-val guide-ref)] + (let [start-pt (mf/ref-val start-ref) + current-pt (dom/get-client-position move-event) + already-moved? (mf/ref-val moved-ref) + past-threshold? + (or already-moved? + (> (+ (mth/abs (- (:x current-pt) (:x start-pt))) + (mth/abs (- (:y current-pt) (:y start-pt)))) + guide-drag-threshold))] + (when past-threshold? + (let [axis (:axis guide) + new-position (compute-guide-drag-position + {:axis axis + :position (:position guide) + :start-pt start-pt + :current-pt current-pt + :zoom zoom + :snap-pixel? snap-pixel?}) + new-frame-id (-> (get-hover-frame) (get :id)) + pending {:guide guide + :new-position new-position + :new-frame-id new-frame-id + :mode :drag}] + (when-not already-moved? + (mf/set-ref-val! moved-ref true) + (when (some? on-guide-drag) + (on-guide-drag (:id guide)))) + (mf/set-ref-val! pending-ref pending) + (reset! state pending))))))) + + editing? + (fn [] (= :edit (:mode @state))) + + guide-at-event + (fn [event] + (when-let [pt (uwvv/point->viewport (dom/get-client-position event))] + (guide-by-serialized-index wasm-guides (wasm.api/find-guide-at pt zoom)))) + + visible-guide-at-event + (fn [event] + (when-let [guide (guide-at-event event)] + (when (guide-visible-in-focus? focus (:frame-id guide)) + guide))) + + guide-frame-offset + (fn [guide] + (let [frame (some-> (:frame-id guide) refs/object-by-id deref)] + (if frame + (if (= :x (:axis guide)) (:x frame) (:y frame)) + 0))) + + pointer-move-hover + (fn [event] + ;; Only update hover cursor / pill when we are not in the middle of + ;; a drag or edit. During drag the cursor is already set to the + ;; dragged guide's axis; during edit the input owns the cursor. + (when (and (not read-only?) + (not (editing?)) + (not (mf/ref-val dragging-ref))) + (let [guide (visible-guide-at-event event) + current-state @state + current-hover-id (when (= :hover (:mode current-state)) + (-> current-state :guide :id))] + (emit-hover-axis (:axis guide)) + (emit-hover-guide-id (:id guide)) + (cond + (and (some? guide) + (not= (:id guide) current-hover-id)) + (let [frame (some-> (:frame-id guide) refs/object-by-id deref)] + (reset! state {:guide guide + :new-position (:position guide) + :frame-offset (guide-frame-offset guide) + ;; Only root, non-rotated frames get the + ;; segmented line (matching the SVG renderer), + ;; so we only overlay dotted extensions there. + :frame (when (and frame + (cfh/root-frame? frame) + (not (ctst/rotated-frame? frame))) + frame) + :mode :hover})) + + (and (nil? guide) (some? current-hover-id)) + (reset! state nil))))) + + pointer-down + (fn [event] + (when (and (not read-only?) (not (editing?))) + ;; While editing, any click outside the input commits the edit + ;; via the input's blur handler. Don't initiate a drag on the + ;; same pointerdown. + (when (= 0 (.-button event)) + (let [client-pos (dom/get-client-position event) + guide (visible-guide-at-event event) + {:keys [id axis position frame-id]} guide] + (when guide + (when-let [viewport @uwvv/viewport-ref] + (.setPointerCapture viewport (.-pointerId event))) + (dom/stop-propagation event) + (emit-hover-axis axis) + (emit-hover-guide-id id) + (mf/set-ref-val! dragging-ref true) + (mf/set-ref-val! moved-ref false) + (mf/set-ref-val! start-ref client-pos) + (mf/set-ref-val! guide-ref guide) + (mf/set-ref-val! pending-ref + {:guide guide + :new-position position + :new-frame-id frame-id + :mode :drag}) + ;; Pointer capture (above) routes all subsequent pointer + ;; events to the viewport, so we listen on the viewport + ;; itself rather than window. This keeps events flowing + ;; even outside the browser window. + (when-let [viewport @uwvv/viewport-ref] + (let [on-move #(drag-move %) + on-up #(finish-drag %)] + (mf/set-ref-val! drag-listeners-ref + {:on-move on-move :on-up on-up}) + (.addEventListener viewport "pointermove" on-move true) + (.addEventListener viewport "pointerup" on-up true) + (.addEventListener viewport "pointercancel" on-up true)))))))) + + double-click + (fn [event] + (when (and (not read-only?) (not (editing?))) + (let [guide (visible-guide-at-event event) + {:keys [id axis position frame-id]} guide] + (when guide + (dom/prevent-default event) + (dom/stop-propagation event) + (when (some? on-guide-drag) + (on-guide-drag id)) + (mf/set-ref-val! guide-ref guide) + (let [frame (some-> frame-id refs/object-by-id deref) + offset (if frame + (if (= :x axis) (:x frame) (:y frame)) + 0)] + (reset! state {:guide guide + :new-position position + :new-frame-id frame-id + :frame-offset offset + :mode :edit})))))) + + commit-edit + (fn [raw-value] + (when (editing?) + (let [{:keys [guide new-frame-id frame-offset]} @state + parsed (some-> raw-value str/trim d/parse-double)] + (when (and (some? parsed) (some? on-guide-change)) + (on-guide-change (assoc guide + :position (+ parsed frame-offset) + :frame-id new-frame-id))) + (reset-state)))) + + cancel-edit + (fn [] + (when (editing?) + (reset-state)))] + + (mf/with-effect [wasm-guides? disabled-guides? read-only? + wasm-guides zoom focus snap-pixel? + on-guide-change on-guide-drag on-guide-hover get-hover-frame] + (when (and wasm-guides? (not disabled-guides?) (not read-only?)) + (when-let [viewport @uwvv/viewport-ref] + (.addEventListener viewport "pointerdown" pointer-down true) + (.addEventListener viewport "pointermove" pointer-move-hover true) + (.addEventListener viewport "dblclick" double-click true) + (fn [] + (.removeEventListener viewport "pointerdown" pointer-down true) + (.removeEventListener viewport "pointermove" pointer-move-hover true) + (.removeEventListener viewport "dblclick" double-click true) + ;; Only tear down state on real teardown. If this cleanup is + ;; triggered by a dependency change mid-interaction, leave the + ;; active drag/edit (and its listeners) untouched so it can + ;; finish. + (when-not (or (mf/ref-val dragging-ref) (editing?)) + (reset-state)))))) + + {:state state + :commit-edit commit-edit + :cancel-edit cancel-edit})) + +(mf/defc wasm-guide-overlay-layer* + "Owns WASM guide drag/edit state and overlay rendering so updates are not + blocked by memoization on `viewport-guides*`." + [{:keys [wasm-guides zoom wasm-guides? disabled-guides? on-guide-change + on-guide-drag on-guide-hover get-hover-frame focus vbox]}] + (let [{:keys [state commit-edit cancel-edit]} + (use-wasm-guide-interaction {:wasm-guides wasm-guides + :zoom zoom + :wasm-guides? wasm-guides? + :disabled-guides? disabled-guides? + :on-guide-change on-guide-change + :on-guide-drag on-guide-drag + :on-guide-hover on-guide-hover + :get-hover-frame get-hover-frame + :focus focus}) + + {:keys [guide new-position mode frame frame-offset]} @state] + + (when (some? guide) + [:> guide-overlay* {:guide guide + :position new-position + :vbox vbox + :zoom zoom + :mode mode + :frame frame + :frame-offset (or frame-offset 0) + :on-input-commit commit-edit + :on-input-cancel cancel-edit}]))) + (mf/defc viewport-guides* {::mf/wrap [mf/memo]} - [{:keys [zoom vbox hover-frame disabled-guides modifiers guides]}] - (let [guides + [{:keys [zoom vbox hover-frame disabled-guides modifiers guides wasm-guides + wasm-guides? on-guide-drag on-guide-hover]}] + (let [visible-guides (mf/with-memo [guides vbox] (->> (vals guides) (filter (partial guide-inside-vbox? zoom vbox)))) @@ -616,6 +1090,23 @@ (st/emit! (dw/show-guide-context-menu {:position position :guide guide}))))) + ;; When guides are WASM-rendered, right-click hit testing is delegated to + ;; the render engine instead of per-guide SVG areas. + on-wasm-context-menu + (mf/use-fn + (mf/deps wasm-guides zoom disabled-guides) + (fn [event] + (when-not disabled-guides + (let [position (dom/get-client-position event) + pt (uwvv/point->viewport position) + index (when pt (wasm.api/find-guide-at pt zoom)) + guide (guide-by-serialized-index wasm-guides index)] + (when guide + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dw/show-guide-context-menu {:position position + :guide guide}))))))) + frame-modifiers (-> (group-by :id modifiers) (update-vals (comp :transform first)))] @@ -623,6 +1114,12 @@ (mf/with-effect [hover-frame] (mf/set-ref-val! hover-frame-ref hover-frame)) + (mf/with-effect [wasm-guides? disabled-guides on-wasm-context-menu] + (when (and wasm-guides? (not disabled-guides)) + (when-let [viewport @uwvv/viewport-ref] + (.addEventListener viewport "contextmenu" on-wasm-context-menu true) + #(.removeEventListener viewport "contextmenu" on-wasm-context-menu true)))) + [:g.guides {:pointer-events "none"} [:> new-guide-area* {:vbox vbox :zoom zoom @@ -636,16 +1133,27 @@ :get-hover-frame get-hover-frame :disabled-guides disabled-guides}] - (for [{:keys [id frame-id] :as guide} guides] - (when (or (nil? frame-id) - (empty? focus) - (contains? focus frame-id)) - [:> guide* {:key (dm/str "guide-" id) - :guide guide - :vbox vbox - :zoom zoom - :frame-transform (get frame-modifiers frame-id) - :get-hover-frame get-hover-frame - :on-guide-change on-guide-change - :on-guide-context-menu on-guide-context-menu - :disabled-guides disabled-guides}]))])) + (when wasm-guides? + [:> wasm-guide-overlay-layer* {:wasm-guides wasm-guides + :zoom zoom + :wasm-guides? wasm-guides? + :disabled-guides? disabled-guides + :on-guide-change on-guide-change + :on-guide-drag on-guide-drag + :on-guide-hover on-guide-hover + :get-hover-frame get-hover-frame + :focus focus + :vbox vbox}]) + + (when-not wasm-guides? + (for [{:keys [id frame-id] :as guide} visible-guides] + (when (guide-visible-in-focus? focus frame-id) + [:> guide* {:key (dm/str "guide-" id) + :guide guide + :vbox vbox + :zoom zoom + :frame-transform (get frame-modifiers frame-id) + :get-hover-frame get-hover-frame + :on-guide-change on-guide-change + :on-guide-context-menu on-guide-context-menu + :disabled-guides disabled-guides}])))])) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 4deaf6fcda..141986c4f2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -321,8 +321,16 @@ (filter #(or (root-frame-with-data? %) (and (cfh/group-shape? objects %) (not (contains? child-parent? %))) + ;; WASM only: drop text from @hover unless the + ;; cursor is over rendered glyphs, so clicks + ;; pass through empty areas of the text box. + ;; Skip this for shapes already in `selected`: + ;; @hover drives on-click, and the first click + ;; of a double-click would otherwise reselect + ;; a parent/container after the pointer moves. (and (features/active-feature? @st/state "render-wasm/v1") (cfh/text-shape? (get objects %)) + (not (contains? selected %)) (not (wasm.api/intersect-position-in-shape % @last-point-ref))))))) remove-measure-xf diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 40546618d8..45d2b5177e 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -117,6 +117,44 @@ (uuid? outlined-frame-id) (conj outlined-frame-id) (uuid? edition) (disj edition)))) +(mf/defc transition-overlay* + "Frozen-frame overlay shown while switching pages or recovering from WebGL + context loss. `image` is either an `ImageBitmap` snapshot of the canvas or + a data-url string placeholder (initial load)." + [{:keys [image clip-path]}] + (let [canvas-ref (mf/use-ref nil) + bitmap? (not (string? image))] + (mf/with-effect [image] + (when bitmap? + (when-let [canvas (mf/ref-val canvas-ref)] + ;; A closed ImageBitmap reports zero size; skip instead of throwing. + (when (pos? (.-width ^js image)) + (set! (.-width ^js canvas) (.-width ^js image)) + (set! (.-height ^js canvas) (.-height ^js image)) + (let [ctx (.getContext ^js canvas "2d")] + (.drawImage ^js ctx image 0 0)))))) + (if bitmap? + ;; Full-bleed so the snapshot overlays the canvas 1:1. + [:canvas {:data-testid "canvas-wasm-transition" + :ref canvas-ref + :style {:position "absolute" + :inset 0 + :width "100%" + :height "100%" + :object-fit "cover" + :pointer-events "none" + :clip-path clip-path}}] + [:img {:data-testid "canvas-wasm-transition" + :src image + :draggable false + :style {:position "absolute" + :inset 0 + :width "100%" + :height "100%" + :object-fit "cover" + :pointer-events "none" + :clip-path clip-path}}]))) + (mf/defc viewport* [{:keys [selected wglobal layout file page palete-size]}] (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check @@ -181,6 +219,16 @@ active-frames (mf/use-state #{}) canvas-init? (mf/use-state false) initialized? (mf/use-state false) + dragging-guide-id* (mf/use-state nil) + guide-hover-axis* (mf/use-state nil) + + on-guide-drag + (mf/use-fn + #(reset! dragging-guide-id* %)) + + on-guide-hover + (mf/use-fn + #(reset! guide-hover-axis* %)) ;; REFS [viewport-ref @@ -342,6 +390,14 @@ disabled-guides? (or drawing-tool transform path-drawing? path-editing? (contains? layout :lock-guides)) + wasm-guides + (mf/with-memo [guides focus show-rulers? show-grids? @dragging-guide-id*] + (guides/wasm-visible-guides + {:guides guides + :visible? (and show-rulers? show-grids?) + :focused focus + :dragging-id @dragging-guide-id*})) + single-select? (= (count selected-shapes) 1) first-shape (first selected-shapes) @@ -375,7 +431,7 @@ preview-blend (-> refs/workspace-preview-blend (mf/deref)) shapes-loading? (mf/deref wasm.api/shapes-loading?) - transition-image-url (mf/deref wasm.api/transition-image-url*)] + transition-image (mf/deref wasm.api/transition-image*)] ;; NOTE: We need this page-id dependency to react to it and reset the ;; canvas, even though we are not using `page-id` inside the hook. @@ -538,6 +594,13 @@ (when @canvas-init? (wasm.api/render-ui-only))) + ;; Ruler guides: push the WASM-visible guide set to the render engine. + ;; `wasm-guides` is also passed to the SVG overlay for index-based hit + ;; testing — it must stay in sync with what we serialize here. + (mf/with-effect [@canvas-init? wasm-guides objects] + (when @canvas-init? + (wasm.api/set-guides wasm-guides objects))) + (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?) @@ -593,25 +656,16 @@ ;; Show the transition image when switching pages or recovering from WebGL context loss. (when (and (or page-transition? context-loss-overlay?) - (some? transition-image-url)) - (let [src transition-image-url] - [:img {:data-testid "canvas-wasm-transition" - :src src - :draggable false - ;; Full-bleed so the snapshot overlays the canvas 1:1. - :style {:position "absolute" - :inset 0 - :width "100%" - :height "100%" - :object-fit "cover" - :pointer-events "none" - ;; Initial load: clip to the live canvas frame (rounded - ;; corner + ruler strips when present) so it shows - ;; through. No frame in hide-UI mode -> no clip. - :clip-path (when (and transition-reveal-rulers? frame-visible?) - (let [strip (if show-rulers? rulers/ruler-area-size 0)] - (dm/str "inset(" strip "px 0 0 " strip "px round " - rulers/canvas-border-radius "px)")))}}])) + (some? transition-image)) + [:> transition-overlay* + {:image transition-image + ;; Initial load: clip to the live canvas frame (rounded + ;; corner + ruler strips when present) so it shows + ;; through. No frame in hide-UI mode -> no clip. + :clip-path (when (and transition-reveal-rulers? frame-visible?) + (let [strip (if show-rulers? rulers/ruler-area-size 0)] + (dm/str "inset(" strip "px 0 0 " strip "px round " + rulers/canvas-border-radius "px)")))}]) [:svg.viewport-controls @@ -622,7 +676,12 @@ :id "viewport-controls" :view-box (utils/format-viewbox vbox) :ref on-viewport-ref - :class (dm/str @cursor (when drawing-tool " drawing") " " (stl/css :viewport-controls)) + :class (dm/str @cursor " " + (stl/css-case + :global/drawing drawing-tool + :global/cursor-resize-ew-0 (= @guide-hover-axis* :x) + :global/cursor-resize-ns-0 (= @guide-hover-axis* :y) + :viewport-controls true)) :style {:touch-action "none"} :fill "none" :on-click on-click @@ -652,9 +711,9 @@ (when show-text-editor? (cond (features/active-feature? @st/state "text-editor-wasm/v1") - [:& editor-v3/text-editor {:shape editing-shape - :canvas-ref canvas-ref - :ref text-editor-ref}] + [:> editor-v3/text-editor* {:shape editing-shape + :canvas-ref canvas-ref + :ref text-editor-ref}] (features/active-feature? @st/state "text-editor/v2") [:& editor-v2/text-editor {:shape editing-shape @@ -725,33 +784,33 @@ :zoom zoom}]) (when show-padding? - [:& mfc/padding-control + [:> mfc/padding-control* {:frame first-shape :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift? + :is-alt @alt? + :is-shift @shift? :on-move-selected on-move-selected :on-context-menu on-menu-selected}]) (when show-padding? - [:& mfc/gap-control + [:> mfc/gap-control* {:frame first-shape :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift? + :is-alt @alt? + :is-shift @shift? :on-move-selected on-move-selected :on-context-menu on-menu-selected}]) (when show-margin? - [:& mfc/margin-control + [:> mfc/margin-control* {:shape first-shape :parent selected-frame :hover @frame-hover :zoom zoom - :alt? @alt? - :shift? @shift?}]) + :is-alt @alt? + :is-shift @shift?}]) (when-not shapes-loading? [:> widgets/frame-titles* @@ -824,14 +883,21 @@ [:& presence/active-cursors {:page-id page-id}]) + ;; NOTE: ruler guides are being migrated to the WASM render engine. + ;; The SVG-overlay rendering is temporarily disabled while we implement + ;; the new path. (when (and show-rulers? show-grids?) [:> guides/viewport-guides* {:zoom zoom :vbox vbox :guides guides + :wasm-guides wasm-guides + :wasm-guides? true :hover-frame guide-frame :disabled-guides disabled-guides? - :modifiers wasm-modifiers}]) + :modifiers wasm-modifiers + :on-guide-drag on-guide-drag + :on-guide-hover on-guide-hover}]) ;; DEBUG LAYOUT DROP-ZONES (when (dbg/enabled? :layout-drop-zones) diff --git a/frontend/src/app/plugins.cljs b/frontend/src/app/plugins.cljs index 779c09b9fd..8937b4dde6 100644 --- a/frontend/src/app/plugins.cljs +++ b/frontend/src/app/plugins.cljs @@ -17,14 +17,17 @@ [app.plugins.grid :as grid] [app.plugins.library :as library] [app.plugins.public-utils] + [app.plugins.register :as preg] [app.plugins.ruler-guides :as rg] [app.plugins.shape :as shape] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) -(defn init-plugins-runtime! +(defn init-plugins-runtime [] - (runtime/initPluginsRuntime (fn [plugin-id] (api/create-context plugin-id)))) + (runtime/initPluginsRuntime (fn [plugin-id] (api/create-context plugin-id))) + ;; Signal that runtime is ready + (preg/signal-runtime-ready)) (defn initialize [] @@ -38,7 +41,7 @@ (rx/observe-on :async) (rx/filter #(features/active-feature? @st/state "plugins/runtime")) (rx/take 1) - (rx/tap init-plugins-runtime!) + (rx/tap init-plugins-runtime) (rx/ignore))))) ;; Prevent circular dependency diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 8c0d07f316..6fe55c6415 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -27,6 +27,7 @@ [app.main.data.workspace.colors :as dwc] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.media :as dwm] + [app.main.data.workspace.pages :as dwpg] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.variants :as dwv] [app.main.data.workspace.wasm-text :as dwwt] @@ -54,7 +55,8 @@ [app.util.object :as obj] [app.util.theme :as theme] [beicon.v2.core :as rx] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [potok.v2.core :as ptk])) ;; ;; PLUGINS PUBLIC API - The plugins will able to access this functions @@ -571,11 +573,20 @@ (let [id (cond (page/page-proxy? page) (obj/get page "$id") (string? page) (uuid/parse* page) - :else nil) - new-window (if (boolean? new-window) new-window false)] + :else nil)] (if (nil? id) (u/not-valid plugin-id :openPage "Expected a Page object or a page UUID string") - (st/emit! (dcm/go-to-workspace :page-id id ::rt/new-window new-window))))) + (if (true? new-window) + (do (st/emit! (dcm/go-to-workspace :page-id id ::rt/new-window true)) + (js/Promise.resolve nil)) + (js/Promise. + (fn [resolve _] + (->> st/stream + (rx/filter (ptk/type? ::dwpg/initialized)) + (rx/filter #(= (deref %) id)) + (rx/take 1) + (rx/subs! #(resolve nil))) + (st/emit! (dcm/go-to-workspace :page-id id)))))))) :alignHorizontal (fn [shapes direction] diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs index 39e0938880..57e37e4a5c 100644 --- a/frontend/src/app/plugins/page.cljs +++ b/frontend/src/app/plugins/page.cljs @@ -19,6 +19,7 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.guides :as dwgu] [app.main.data.workspace.interactions :as dwi] + [app.main.data.workspace.pages :as dwpg] [app.main.repo :as rp] [app.main.router :as-alias rt] [app.main.store :as st] @@ -32,7 +33,8 @@ [app.plugins.utils :as u] [app.util.object :as obj] [beicon.v2.core :as rx] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [potok.v2.core :as ptk])) (declare page-proxy) @@ -269,9 +271,19 @@ (not (r/check-permission plugin-id "content:read")) (u/not-valid plugin-id :openPage "Plugin doesn't have 'content:read' permission") + (true? new-window) + (do (st/emit! (dcm/go-to-workspace :page-id id ::rt/new-window true)) + (js/Promise.resolve nil)) + :else - (let [new-window (if (boolean? new-window) new-window false)] - (st/emit! (dcm/go-to-workspace :page-id id ::rt/new-window new-window))))) + (js/Promise. + (fn [resolve _] + (->> st/stream + (rx/filter (ptk/type? ::dwpg/initialized)) + (rx/filter #(= (deref %) id)) + (rx/take 1) + (rx/subs! #(resolve nil))) + (st/emit! (dcm/go-to-workspace :page-id id)))))) :createFlow (fn [name frame] diff --git a/frontend/src/app/plugins/register.cljs b/frontend/src/app/plugins/register.cljs index d05b08d547..e4837ce75b 100644 --- a/frontend/src/app/plugins/register.cljs +++ b/frontend/src/app/plugins/register.cljs @@ -15,12 +15,28 @@ [app.main.repo :as rp] [app.main.store :as st] [app.util.object :as obj] - [beicon.v2.core :as rx])) + [beicon.v2.core :as rx] + [promesa.core :as p])) ;; Needs to be here because moving it to `app.main.data.workspace.mcp` will ;; cause a circular dependency (def mcp-plugin-id "96dfa740-005d-8020-8007-55ede24a2bae") +;; Promise that resolves when plugins runtime is initialized. +;; Lives here to avoid circular dependency: workspace.mcp -> app.plugins -> app.plugins.api -> workspace +(defonce ^:private runtime-ready-promise (p/deferred)) + +(defn wait-for-runtime + "Returns a promise that resolves when plugins runtime is initialized." + [] + runtime-ready-promise) + +(defn signal-runtime-ready + "Signals that plugins runtime has been initialized. Called by app.plugins/init-plugins-runtime." + [] + (when (p/pending? runtime-ready-promise) + (p/resolve! runtime-ready-promise true))) + ;; Stores the installed plugins information (defonce ^:private registry (atom {})) diff --git a/frontend/src/app/rasterizer.cljs b/frontend/src/app/rasterizer.cljs index 66f906686f..ed516fc85f 100644 --- a/frontend/src/app/rasterizer.cljs +++ b/frontend/src/app/rasterizer.cljs @@ -49,7 +49,14 @@ :hint "operation aborted"))) (obj/set! image "src" uri) (fn [] - (obj/set! image "src" "") + ;; NOTE: We intentionally do NOT set `image.src = ""` here. + ;; Doing so discards the decoded pixel data immediately when this + ;; observable completes, but downstream operators (e.g. drawImage / + ;; createImageBitmap in `render-image-bitmap`) still need to read + ;; the pixel data after the image is emitted. Clearing `src` before + ;; the downstream pipeline finishes causes a NotReadableError + ;; ("The requested file could not be read..."). The image element + ;; will be garbage collected naturally when no references remain. (obj/set! image "onload" nil) (obj/set! image "onerror" nil) (obj/set! image "onabort" nil)))))) @@ -57,10 +64,13 @@ (defn- svg-get-adjusted-size "Returns the adjusted size of an SVG." [width height max] - (let [ratio (/ width height)] - (if (< width height) - [max (* max (/ 1 ratio))] - [(* max ratio) max]))) + ;; Guard against zero/NaN dimensions that would cause division by zero + ;; or produce invalid sizes, leading to createImageBitmap failures. + (when (and (pos? width) (pos? height)) + (let [ratio (/ width height)] + (if (< width height) + [max (* max (/ 1 ratio))] + [(* max ratio) max])))) (defn- svg-get-size-from-viewbox "Returns the size of an SVG from its viewbox." @@ -101,7 +111,10 @@ "Sets the intrinsic size of an SVG to the given max size." [^js svg max] (let [doc (get-document-element svg) - [w h] (svg-get-size svg max)] + [w h] (or (svg-get-size svg max) + ;; Fallback: if we can't determine size from viewBox or + ;; intrinsic attributes, use the max size as a square. + [max max])] (dom/set-attribute! doc "width" (dm/str w)) (dom/set-attribute! doc "height" (dm/str h))) svg) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index c4ae182fc0..1b609a53f3 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -70,9 +70,9 @@ ;; `penpot:wasm:tiles-complete`. ;; ;; - `page-transition?`: true while the overlay should be considered active. -;; - `transition-image-url*`: URL used by the UI overlay (usually `blob:` from the -;; current WebGL canvas snapshot; on initial load it may be a tiny SVG data-url -;; derived from the page background color). +;; - `transition-image*`: image shown by the UI overlay (usually an `ImageBitmap` +;; snapshot of the WebGL canvas; on initial load it may be a tiny SVG data-url +;; string derived from the page background color). ;; - `transition-epoch*`: monotonic counter used to ignore stale async work/events ;; when the user clicks pages rapidly (A -> B -> C). ;; - `transition-tiles-handler*`: the currently installed DOM event handler for @@ -83,7 +83,7 @@ ;; rulers show through. False (page switch / context loss) keeps the snapshot's ;; baked-in rulers full-bleed to avoid a blank-strip flicker on canvas remount. (defonce transition-reveal-rulers? (atom false)) -(defonce transition-image-url* (atom nil)) +(defonce transition-image* (atom nil)) (defonce transition-epoch* (atom 0)) (defonce transition-tiles-handler* (atom nil)) (defonce snapshot-tiles-handler* (atom nil)) @@ -100,13 +100,13 @@ (defn set-transition-image-from-background! - "Sets `transition-image-url*` to a data URL representing a solid background color." + "Sets `transition-image*` to a data URL representing a solid background color." [background] (when (string? background) (let [svg (str "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'>" "<rect width='1' height='1' fill='" background "'/>" "</svg>")] - (reset! transition-image-url* + (reset! transition-image* (str "data:image/svg+xml;charset=utf-8," (js/encodeURIComponent svg)))))) (defn begin-page-transition! @@ -120,7 +120,7 @@ (when-let [prev @transition-tiles-handler*] (.removeEventListener ^js ug/document "penpot:wasm:tiles-complete" prev)) (reset! transition-tiles-handler* nil) - (reset! transition-image-url* nil)) + (reset! transition-image* nil)) (defn- set-transition-tiles-complete-handler! "Installs a tiles-complete handler bound to the current transition epoch. @@ -146,7 +146,7 @@ (reset! transition-reveal-rulers? true) ; reveal the live rulers ;; If something already toggled `page-transition?` (e.g. legacy init code paths), ;; ensure we still have a deterministic placeholder on initial load. - (when (or (not @page-transition?) (nil? @transition-image-url*)) + (when (or (not @page-transition?) (nil? @transition-image*)) (set-transition-image-from-background! background)) (when-not @page-transition? ;; Start transition + bind the tiles-complete handler to this epoch. @@ -161,7 +161,7 @@ [] (reset! context-loss-overlay? false) (when-not @page-transition? - (reset! transition-image-url* nil))) + (reset! transition-image* nil))) (defn listen-tiles-render-complete-once! "Registers a one-shot listener for `penpot:wasm:tiles-complete`, dispatched from WASM @@ -173,12 +173,28 @@ (f)) #js {:once true})) +(defn capture-canvas-snapshot + "Captures the viewport canvas into `wasm/canvas-snapshot` (an `ImageBitmap`) + and closes the replaced snapshot unless the transition overlay is still + showing it (a replaced snapshot can never become displayed again, so closing + it is safe). Returns a promise resolving to the bitmap (or nil)." + [] + (let [^js prev wasm/canvas-snapshot] + (-> (webgl/capture-canvas-snapshot) + (p/then (fn [^js bitmap] + (when (and (some? prev) + (some? bitmap) + (not (identical? prev bitmap)) + (not (identical? prev @transition-image*))) + (.close prev)) + bitmap))))) + (defonce ^:private schedule-canvas-snapshot-capture! (fns/debounce (fn [] (when (and (initialized?) (some? wasm/canvas)) - (-> (webgl/capture-canvas-snapshot-url) + (-> (capture-canvas-snapshot) (p/catch (fn [_] nil))))) snapshot-capture-debounce-ms)) @@ -239,7 +255,6 @@ (def ^:const TEXT_EDITOR_EVENT_NEEDS_LAYOUT 4) ;; Re-export public WebGL functions -(def capture-canvas-snapshot-url webgl/capture-canvas-snapshot-url) (def draw-thumbnail-to-canvas webgl/draw-thumbnail-to-canvas) ;; Re-export public text editor functions @@ -432,7 +447,7 @@ (defn render-blurred-snapshot! "Blurs the current page into the canvas so a following - `capture-canvas-snapshot-url` grabs an already-blurred transition frame." + `capture-canvas-snapshot` grabs an already-blurred transition frame." [] (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render_blurred_snapshot" TRANSITION_BLUR_RADIUS))) @@ -931,6 +946,13 @@ [hidden] (h/call wasm/internal-module "_set_shape_hidden" hidden)) +(defn clear-shape-fills! + "Clear the fills of the currently-selected shape (call `use-shape` first). + Equivalent to `set-shape-fills` with an empty collection." + [] + (when (initialized?) + (h/call wasm/internal-module "_clear_shape_fills"))) + (defn set-shape-bool-type [bool-type] (h/call wasm/internal-module "_set_shape_bool_type" (sr/translate-bool-type bool-type))) @@ -1411,6 +1433,24 @@ (let [{:keys [thumbnails full]} (set-object shape)] (process-pending [shape] thumbnails full noop-fn))) +(defn process-objects + "Like process-object but for multiple shapes at once. Accumulates all + pending font/image callbacks before calling process-pending, so that + update-text-layouts fires for all text shapes after fonts load — not + just the first shape that triggered the fetch." + [shapes] + (let [total-shapes (count shapes) + {:keys [thumbnails full]} + (loop [index 0 thumbnails-acc (transient []) full-acc (transient [])] + (if (< index total-shapes) + (let [shape (nth shapes index) + {:keys [thumbnails full]} (set-object shape)] + (recur (inc index) + (reduce conj! thumbnails-acc thumbnails) + (reduce conj! full-acc full))) + {:thumbnails (persistent! thumbnails-acc) :full (persistent! full-acc)}))] + (process-pending shapes thumbnails full noop-fn))) + (defn- process-shapes-chunk "Process shapes starting at `start-index` until the time budget is exhausted. Returns {:thumbnails [...] :full [...] :next-index n}" @@ -1666,6 +1706,27 @@ (h/call wasm/internal-module "_set_focus_mode") (request-render "set-focus-mode")))) +(defn clear-render-include-filter! + "Clear the viewer include filter (render all shapes in the subtree again)." + [] + (when (initialized?) + (h/call wasm/internal-module "_clear_render_include_filter"))) + +(defn set-render-include-filter! + "Restrict the next render to `shape-ids` and descendants of whitelisted nodes. + Used for viewer fixed-scroll layers; does not change shape hidden flags." + [shape-ids] + (when (and (initialized?) (seq shape-ids)) + (let [ids (vec shape-ids) + size (mem/get-alloc-size ids UUID-U8-SIZE) + heap (mem/get-heap-u32) + offset (mem/alloc->offset-32 size)] + (reduce (fn [offset id] + (mem.h32/write-uuid offset heap id)) + offset + ids) + (h/call wasm/internal-module "_set_render_include_filter")))) + (defn set-structure-modifiers [entries] (when-not ^boolean (empty? entries) @@ -1865,6 +1926,15 @@ [width height] (h/call wasm/internal-module "_resize_viewbox" width height)) +(defn set-viewer-viewport! + "Update viewer zoom/pan and rebuild the tile index (frame hops in the viewer). + `vbox` must have at least `:x` and `:y` keys (design-space top-left corner)." + [zoom vbox] + (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) + (when (initialized?) + (h/call wasm/internal-module "_set_view_end") + (reset! view-interaction-active? false))) + (defn- debug-flags [] (cond-> 0 @@ -1875,6 +1945,22 @@ (contains? cf/flags :render-wasm-info) (bit-or 2r00000000000000000000000000001000))) +(defn set-render-options! + "Updates WASM render options with a new DPR value." + [new-dpr] + (h/call wasm/internal-module "_set_render_options" (debug-flags) new-dpr)) + +(defn resize-offscreen-canvas! + "Resize a persistent OffscreenCanvas to new physical-pixel dimensions and + update the WASM render surfaces accordingly (via `_resize_viewbox`). The + design state (shape pool) is preserved so `set-objects` is not needed again." + [canvas new-physical-w new-physical-h] + (let [dpr (get-dpr)] + (set! (.-width canvas) new-physical-w) + (set! (.-height canvas) new-physical-h) + (set-render-options! dpr) + (resize-viewbox (/ new-physical-w dpr) (/ new-physical-h dpr)))) + (defn- wasm-get-numeric-value [name] (when-let [raw (let [p (rt/get-params @st/state)] @@ -1889,11 +1975,6 @@ (let [setter-name (str/concat "_set_" (name param-name))] (h/call wasm/internal-module setter-name value)))) -(defn set-render-options! - "Updates WASM render options with a new DPR value." - [new-dpr] - (h/call wasm/internal-module "_set_render_options" (debug-flags) new-dpr)) - (defn- canvas-css-size "Return canvas size in CSS pixels. @@ -1930,9 +2011,8 @@ ;; Keep the last rendered pixels visible while context is lost/recovering. (reset! transition-reveal-rulers? false) ; snapshot has rulers baked in (start-context-loss-overlay!) - (when-let [url wasm/canvas-snapshot-url] - (when (string? url) - (reset! transition-image-url* url))) + (when-let [snapshot wasm/canvas-snapshot] + (reset! transition-image* snapshot)) (reset! wasm/context-lost? true) (st/async-emit! (ntf/show {:content (tr "webgl.webgl-context-lost.toast") @@ -2176,6 +2256,35 @@ (h/call wasm/internal-module "_hide_grid") (request-render "clear-grid")) +;; Ruler guides ---------------------------------------------------------------- + +(defn set-guides + "Serializes the page guides and sends them to the render engine. + `guides` is the page `:guides` map (id -> guide); `objects` is the page + objects map, used to resolve each guide's board clip range." + [guides objects] + (let [size (sr/get-guides-byte-size guides) + offset (mem/alloc->offset-32 size) + heapu32 (mem/get-heap-u32) + heapf32 (mem/get-heap-f32)] + (sr/write-guides guides objects heapu32 heapf32 offset) + (h/call wasm/internal-module "_set_guides") + (request-render "set-guides"))) + +;; Screen-space hit tolerance for ruler guides. Must match +;; `guide-active-area` in `app.main.ui.workspace.viewport.guides`. +(def ^:private guide-active-area 16) + +(defn find-guide-at + "Returns the serialized guide index at `position` (viewport coordinates), + or -1 when no guide is within the hit tolerance." + [position zoom] + (h/call wasm/internal-module "_find_guide_at" + (:x position) + (:y position) + zoom + guide-active-area)) + (defn get-grid-coords [position] (let [offset (h/call wasm/internal-module @@ -2351,12 +2460,11 @@ ;; Lock the snapshot for the whole transition: if the user clicks to another page ;; while the transition is active, keep showing the original page snapshot until ;; the final target page finishes rendering. The caller (sitemap on-click) is - ;; responsible for ensuring `wasm/canvas-snapshot-url` was freshly captured + ;; responsible for ensuring `wasm/canvas-snapshot` was freshly captured ;; before invoking us. (when-not already? - (when-let [url wasm/canvas-snapshot-url] - (when (string? url) - (reset! transition-image-url* url)))))) + (when-let [snapshot wasm/canvas-snapshot] + (reset! transition-image* snapshot))))) (defn render-shape-pixels [shape-id scale] @@ -2395,6 +2503,25 @@ (mem/free) {:x x :y y :width w :height h})))) +(defn render-shape-pdf + [shape-id scale] + (let [buffer (uuid/get-u32 shape-id) + + offset + (h/call wasm/internal-module "_render_shape_pdf" + (aget buffer 0) + (aget buffer 1) + (aget buffer 2) + (aget buffer 3) + scale) + + heap (mem/get-heap-u8) + heapu32 (mem/get-heap-u32) + length (aget heapu32 (mem/->offset-32 offset)) + result (dr/read-image-bytes heap (+ offset 4) length)] + (mem/free) + result)) + (defn init-wasm-module [module] (let [default-fn (unchecked-get module "default") diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index 443b2498b9..676a00cf78 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -100,6 +100,7 @@ matching-font (some (fn [[_ font]] (and (= (:font-id font) font-uuid) (= (str (:font-weight font)) (str font-weight)) + (= (:font-style font) font-style) font)) (seq @fonts))] (when matching-font diff --git a/frontend/src/app/render_wasm/api/webgl.cljs b/frontend/src/app/render_wasm/api/webgl.cljs index 853370335e..23b8d57810 100644 --- a/frontend/src/app/render_wasm/api/webgl.cljs +++ b/frontend/src/app/render_wasm/api/webgl.cljs @@ -134,28 +134,20 @@ void main() { (.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil) (.deleteTexture ^js gl texture)))) -(defn capture-canvas-snapshot-url - "Captures the current viewport canvas as a PNG `blob:` URL and stores it in - `wasm/canvas-snapshot-url`. +(defn capture-canvas-snapshot + "Captures the current viewport canvas as an `ImageBitmap` and stores it in + `wasm/canvas-snapshot`. Unlike `canvas.toBlob` (which does a synchronous GPU + readback plus PNG encoding on the main thread), `createImageBitmap` resolves + asynchronously and stays on the GPU in accelerated browsers. - Returns a promise resolving to the URL string (or nil)." + Returns a promise resolving to the ImageBitmap (or nil)." [] (if-let [^js canvas wasm/canvas] - (p/create - (fn [resolve _reject] - ;; Revoke previous snapshot to avoid leaking blob URLs. - (when-let [prev wasm/canvas-snapshot-url] - (when (and (string? prev) (.startsWith ^js prev "blob:")) - (js/URL.revokeObjectURL prev))) - (set! wasm/canvas-snapshot-url nil) - (.toBlob canvas - (fn [^js blob] - (if blob - (let [url (js/URL.createObjectURL blob)] - (set! wasm/canvas-snapshot-url url) - (resolve url)) - (resolve nil))) - "image/png"))) + (-> (js/createImageBitmap canvas) + (p/then (fn [^js bitmap] + (set! wasm/canvas-snapshot bitmap) + bitmap)) + (p/catch (fn [_] nil))) (p/resolved nil))) (defn draw-thumbnail-to-canvas diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 4ce4c77400..f30f12d81c 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -8,7 +8,11 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] + [app.common.types.color :as clr] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] + [app.render-wasm.serializers.color :as sr-clr] [app.render-wasm.wasm :as wasm] [cuerdas.core :as str])) @@ -281,3 +285,76 @@ (let [values (unchecked-get wasm/serializers "transform-entry-kind") default (unchecked-get values "parent")] (d/nilv (unchecked-get values (d/name kind)) default))) + +;; --- Guides + +;; Each guide is serialized as 5 x 32-bit words: +;; kind (u32) | color (u32 argb) | position (f32) | frame-start (f32) | frame-end (f32) +;; `frame-start`/`frame-end` hold the board clip range (along the guide's line +;; direction); they are NaN when the guide is not bound to a board. +(def ^:private guide-entry-size 20) + +;; Default guide color used when a guide has no explicit color (matches the +;; previous SVG overlay `default-guide-color`). +(def ^:private default-guide-color clr/new-danger) + +;; Sentinel clip range for a guide whose board is rotated or not a root frame. +;; The engine clips each guide to `[start end]` and skips drawing when `start > +;; end`; both bounds at +Infinity collapse to an empty range, so the guide is +;; hidden (matching the SVG renderer, which drops it from the DOM). +;; We hide via the range rather than by removing the guide from the set so the +;; guide count stays stable and board rendering is unaffected. +(def ^:private guide-hidden-range [js/Infinity js/Infinity]) + +(defn- translate-guide-axis + "Maps a guide axis to the RawGuideKind discriminant expected by WASM. + `:x` (constant x) is a vertical guide, `:y` is a horizontal one." + [axis] + (let [values (unchecked-get wasm/serializers "guide-kind") + default (unchecked-get values "vertical")] + (case axis + :x (unchecked-get values "vertical") + :y (unchecked-get values "horizontal") + default))) + +(defn get-guides-byte-size + "Total heap size (in bytes) needed to serialize `guides` (a map id -> guide), + including the 4-byte header that holds the guide count." + [guides] + (+ 4 (* (count (or guides {})) guide-entry-size))) + +(defn- guide-frame-range + "Returns the `[start end]` clip range (along the guide's line direction) for a + board-bound guide: the board's y-range for vertical `:x` guides, its x-range + for horizontal `:y` guides. Returns nil for free guides (drawn full-length). + A guide bound to a rotated or non-root board returns `guide-hidden-range`, an + empty range the engine clips out, hiding it like the SVG renderer does." + [guide objects] + (when-let [frame (some->> (get guide :frame-id) (get objects))] + (if (and (cfh/root-frame? frame) + (not (ctst/rotated-frame? frame))) + (if (= :x (get guide :axis)) + [(:y frame) (+ (:y frame) (:height frame))] + [(:x frame) (+ (:x frame) (:width frame))]) + guide-hidden-range))) + +(defn write-guides + "Writes `guides` (a map id -> guide) into the heap views starting at the + 32-bit `offset`. Layout: count header (u32) followed by + `kind | color | position | frame-start | frame-end` per guide. The frame + range is resolved from `objects` and written as NaN for free guides." + [guides objects heapu32 heapf32 offset] + (let [guides (vec (vals (or guides {}))) + total (count guides)] + (aset heapu32 offset total) + (loop [i 0] + (when (< i total) + (let [guide (nth guides i) + base (+ offset 1 (* i 5)) + [frame-start frame-end] (guide-frame-range guide objects)] + (aset heapu32 base (translate-guide-axis (get guide :axis))) + (aset heapu32 (+ base 1) (sr-clr/hex->u32argb (or (get guide :color) default-guide-color) 1)) + (aset heapf32 (+ base 2) (get guide :position)) + (aset heapf32 (+ base 3) (or frame-start js/NaN)) + (aset heapf32 (+ base 4) (or frame-end js/NaN))) + (recur (inc i)))))) diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index caf3d3a15a..93c26567ab 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -13,9 +13,10 @@ ;; Reference to the HTML canvas element. (defonce canvas nil) -;; Snapshot of the current canvas suitable for `<img src=...>` overlays. -;; This is typically a `blob:` URL created via `canvas.toBlob`. -(defonce canvas-snapshot-url nil) +;; Snapshot of the current canvas as an `ImageBitmap`, suitable for painting +;; into an overlay canvas. Created via `createImageBitmap` so capturing never +;; encodes pixels on the main thread. +(defonce canvas-snapshot nil) ;; Reference to the Emscripten GL context wrapper. (defonce gl-context-handle nil) @@ -38,7 +39,7 @@ [] (set! internal-frame-id nil) (set! canvas nil) - (set! canvas-snapshot-url nil) + (set! canvas-snapshot nil) (set! gl-context-handle nil) (set! gl-context nil) (set! context-initialized? false) @@ -62,6 +63,7 @@ :wrap-type shared/RawWrapType :grid-track-type shared/RawGridTrackType :shadow-style shared/RawShadowStyle + :guide-kind shared/RawGuideKind :stroke-style shared/RawStrokeStyle :stroke-cap shared/RawStrokeCap :shape-type shared/RawShapeType diff --git a/frontend/src/app/util/browser_history.js b/frontend/src/app/util/browser_history.js index 52831614b3..3bfdcb49ec 100644 --- a/frontend/src/app/util/browser_history.js +++ b/frontend/src/app/util/browser_history.js @@ -40,7 +40,7 @@ goog.scope(function() { }; self.set_token_BANG_ = function(instance, token) { - instance.setToken(token); + instance?.setToken(token); } self.replace_token_BANG_ = function(instance, token) { diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 2b94869245..b80a385ad5 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -842,10 +842,11 @@ ([uri name] (open-new-window uri name "noopener,noreferrer")) ([uri name features] - (when-let [new-window (.open js/window (str uri) name features)] - (when (not= name "_blank") - (when-let [location (.-location new-window)] - (.reload location)))))) + (when (exists? js/window) + (when-let [new-window (.open js/window (str uri) name features)] + (when (not= name "_blank") + (when-let [location (.-location new-window)] + (.reload location))))))) (defn browser-back [] diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index 77f15664ba..be61548a1b 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -24,5 +24,5 @@ (defn ^:export plugins [] (st/emit! (features/enable-feature "plugins/runtime")) - (plugins/init-plugins-runtime!) + (plugins/init-plugins-runtime) nil) diff --git a/frontend/test/frontend_tests/data/workspace_mcp_test.cljs b/frontend/test/frontend_tests/data/workspace_mcp_test.cljs new file mode 100644 index 0000000000..e81dab6780 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_mcp_test.cljs @@ -0,0 +1,50 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.data.workspace-mcp-test + (:require + [app.main.data.workspace.mcp :as mcp] + [cljs.test :as t :include-macros true] + [potok.v2.core :as ptk])) + +(t/deftest test-set-mcp-active + (t/testing "sets :active to true" + (let [state {:mcp {:active false}} + result (ptk/update (mcp/set-mcp-active true) state)] + (t/is (true? (get-in result [:mcp :active]))))) + + (t/testing "sets :active to false" + (let [state {:mcp {:active true}} + result (ptk/update (mcp/set-mcp-active false) state)] + (t/is (false? (get-in result [:mcp :active])))))) + +(t/deftest test-update-mcp-status + (t/testing "enables MCP in profile props" + (let [state {:profile {:props {:mcp-enabled false}}} + result (ptk/update (mcp/update-mcp-status true) state)] + (t/is (true? (get-in result [:profile :props :mcp-enabled]))))) + + (t/testing "disables MCP in profile props" + (let [state {:profile {:props {:mcp-enabled true}}} + result (ptk/update (mcp/update-mcp-status false) state)] + (t/is (false? (get-in result [:profile :props :mcp-enabled])))))) + +(t/deftest test-update-mcp-connection-status + (t/testing "sets connection status to connected" + (let [state {:mcp {:connection-status "disconnected"}} + result (ptk/update (mcp/update-mcp-connection-status "connected") state)] + (t/is (= "connected" (get-in result [:mcp :connection-status]))))) + + (t/testing "sets connection status to disconnected" + (let [state {:mcp {:connection-status "connected"}} + result (ptk/update (mcp/update-mcp-connection-status "disconnected") state)] + (t/is (= "disconnected" (get-in result [:mcp :connection-status])))))) + +(t/deftest test-init-sets-active + (t/testing "init sets :mcp :active to true" + (let [state {:mcp {:active false}} + result (ptk/update (mcp/init) state)] + (t/is (true? (get-in result [:mcp :active])))))) diff --git a/frontend/test/frontend_tests/data/workspace_texts_test.cljs b/frontend/test/frontend_tests/data/workspace_texts_test.cljs index bbb1fe2421..15db703ef8 100644 --- a/frontend/test/frontend_tests/data/workspace_texts_test.cljs +++ b/frontend/test/frontend_tests/data/workspace_texts_test.cljs @@ -7,9 +7,13 @@ (ns frontend-tests.data.workspace-texts-test (:require [app.common.geom.rect :as grc] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.shapes :as cths] [app.common.types.shape :as cts] + [app.common.types.text :as txt] [app.main.data.workspace.texts :as dwt] - [cljs.test :as t :include-macros true])) + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.state :as ths])) ;; --------------------------------------------------------------------------- ;; Helpers @@ -272,3 +276,101 @@ result (dwt/apply-text-modifier shape {:width 200 :position-data pd})] (t/is (some? result)) (t/is (some? (:selrect result)))))) + +;; --------------------------------------------------------------------------- +;; Tests: add-typography event normalises float line-height / letter-spacing +;; +;; These tests exercise the full add-typography event path in texts.cljs: +;; a text shape whose content nodes carry float line-height / letter-spacing +;; values (as produced by JS float arithmetic) must yield a typography entry +;; in the file with properly-rounded *string* values, not the raw floats. +;; +;; Root cause reproduced here: JS float arithmetic (e.g. 1.3 - 0.1) can +;; produce 1.2000000000000002 instead of 1.2. Without the fix, that number +;; survives through add-typography unchanged, and (ctt/check-typography) +;; would throw because :line-height must be a :string. +;; With the fix (mth/precision + str), it is normalised to "1.2" before the +;; schema check runs. +;; --------------------------------------------------------------------------- + +(t/deftest add-typography-normalises-float-line-height + (t/async + done + (let [;; Exact value reproduced from the issue: 1.3 - 0.1 in JS + float-lh 1.2000000000000002 + content (txt/change-text nil "hello" :line-height float-lh) + file (-> (cthf/sample-file :file1) + (cths/add-sample-shape :text1 + :type :text + :x 0 :y 0 + :content content)) + shape-id (:id (cths/get-shape file :text1)) + file-id (:id file) + store (ths/setup-store file) + events [;; Pre-select the text shape so add-typography can find it + (fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) + (dwt/add-typography file-id)]] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + typographies (vals (get-in file' [:data :typographies]))] + (t/is (= 1 (count typographies)) + "exactly one typography was added") + (t/is (= "1.2" (:line-height (first typographies))) + "float line-height is normalised to 2-decimal string"))))))) + +(t/deftest add-typography-truncates-line-height-to-two-decimals + (t/async + done + (let [;; A value with more than 2 decimal places: 1.234234234 → "1.23" + long-lh 1.234234234 + content (txt/change-text nil "hello" :line-height long-lh) + file (-> (cthf/sample-file :file1) + (cths/add-sample-shape :text1 + :type :text + :x 0 :y 0 + :content content)) + shape-id (:id (cths/get-shape file :text1)) + file-id (:id file) + store (ths/setup-store file) + events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) + (dwt/add-typography file-id)]] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + typographies (vals (get-in file' [:data :typographies]))] + (t/is (= 1 (count typographies)) + "exactly one typography was added") + (t/is (= "1.23" (:line-height (first typographies))) + "line-height with more than 2 decimals is truncated to 2"))))))) + +(t/deftest add-typography-normalises-float-letter-spacing + (t/async + done + (let [;; Analogous imprecision for letter-spacing + float-ls 0.10000000000000001 + content (txt/change-text nil "hello" :letter-spacing float-ls) + file (-> (cthf/sample-file :file1) + (cths/add-sample-shape :text1 + :type :text + :x 0 :y 0 + :content content)) + shape-id (:id (cths/get-shape file :text1)) + file-id (:id file) + store (ths/setup-store file) + events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) + (dwt/add-typography file-id)]] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + typographies (vals (get-in file' [:data :typographies]))] + (t/is (= 1 (count typographies)) + "exactly one typography was added") + (t/is (= "0.1" (:letter-spacing (first typographies))) + "float letter-spacing is normalised to 2-decimal string"))))))) diff --git a/frontend/test/frontend_tests/data/workspace_thumbnails_test.cljs b/frontend/test/frontend_tests/data/workspace_thumbnails_test.cljs index f9c21e9be5..2ccf1c51fc 100644 --- a/frontend/test/frontend_tests/data/workspace_thumbnails_test.cljs +++ b/frontend/test/frontend_tests/data/workspace_thumbnails_test.cljs @@ -6,9 +6,18 @@ (ns frontend-tests.data.workspace-thumbnails-test (:require + [app.common.thumbnails :as thc] [app.common.uuid :as uuid] [app.main.data.workspace.thumbnails :as thumbnails] - [cljs.test :as t :include-macros true])) + [beicon.v2.core :as rx] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.mock :as mock] + [potok.v2.core :as ptk])) + +;; The qualified keyword used internally by app.main.data.workspace.thumbnails +;; for tracking the pending deletion queue in application state. +(def ^:private deletion-queue-key + :app.main.data.workspace.thumbnails/thumbnails-deletion-queue) (t/deftest extract-frame-changes-handles-cyclic-frame-links (let [page-id (uuid/next) @@ -33,4 +42,257 @@ :component-root true}}}}}] (t/is (= #{["frame" root-id] ["component" shape-b-id]} - (#'thumbnails/extract-frame-changes page-id [event [old-data new-data]]))))) + (#'thumbnails/extract-frame-changes page-id [event [old-data new-data]])))) + + ;; --- Batch deletion queue state management --- + + (t/deftest clear-thumbnail-adds-to-deletion-queue + (let [file-id (uuid/next) + object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri "blob:http://localhost/test-thumb" + event (thumbnails/clear-thumbnail file-id object-id) + state {:thumbnails {object-id uri}} + result (ptk/update event state)] + ;; Thumbnail removed from the map + (t/is (nil? (get-in result [:thumbnails object-id]))) + ;; Object-id added to the deletion queue with its URI + (t/is (= uri (get-in result [deletion-queue-key object-id]))))) + + (t/deftest clear-thumbnail-keeps-other-thumbnails + (let [file-id (uuid/next) + object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri1 "blob:http://localhost/thumb-1" + uri2 "blob:http://localhost/thumb-2" + event (thumbnails/clear-thumbnail file-id object-id1) + state {:thumbnails {object-id1 uri1 object-id2 uri2}} + result (ptk/update event state)] + ;; Only the cleared thumbnail is removed + (t/is (nil? (get-in result [:thumbnails object-id1]))) + (t/is (= uri2 (get-in result [:thumbnails object-id2]))) + ;; Only the cleared thumbnail is queued + (t/is (= uri1 (get-in result [deletion-queue-key object-id1]))) + (t/is (nil? (get-in result [deletion-queue-key object-id2]))))) + + (t/deftest clear-thumbnail-accumulates-in-queue + (let [file-id (uuid/next) + object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri1 "blob:http://localhost/thumb-1" + uri2 "blob:http://localhost/thumb-2" + event1 (thumbnails/clear-thumbnail file-id object-id1) + event2 (thumbnails/clear-thumbnail file-id object-id2) + state {:thumbnails {object-id1 uri1 object-id2 uri2}} + state1 (ptk/update event1 state) + state2 (ptk/update event2 state1)] + ;; Both removed from thumbnails + (t/is (nil? (get-in state2 [:thumbnails object-id1]))) + (t/is (nil? (get-in state2 [:thumbnails object-id2]))) + ;; Both accumulated in the queue + (t/is (= uri1 (get-in state2 [deletion-queue-key object-id1]))) + (t/is (= uri2 (get-in state2 [deletion-queue-key object-id2]))) + (t/is (= 2 (count (get state2 deletion-queue-key)))))) + + (t/deftest remove-from-deletion-queue-removes-entry + (let [file-id (uuid/next) + object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + event (thumbnails/remove-from-deletion-queue object-id) + state {deletion-queue-key {object-id "blob:http://localhost/thumb"}} + result (ptk/update event state)] + (t/is (nil? (get-in result [deletion-queue-key object-id]))) + (t/is (empty? (get result deletion-queue-key))))) + + (t/deftest remove-from-deletion-queue-keeps-other-entries + (let [file-id (uuid/next) + object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri1 "blob:http://localhost/thumb-1" + uri2 "blob:http://localhost/thumb-2" + event (thumbnails/remove-from-deletion-queue object-id1) + state {deletion-queue-key {object-id1 uri1 + object-id2 uri2}} + result (ptk/update event state)] + ;; Only the specified entry is removed + (t/is (nil? (get-in result [deletion-queue-key object-id1]))) + (t/is (= uri2 (get-in result [deletion-queue-key object-id2]))) + (t/is (= 1 (count (get result deletion-queue-key)))))) + + (t/deftest remove-before-clear-cancels-pending-delete + (let [file-id (uuid/next) + object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri "blob:http://localhost/thumb" + ;; Step 1: clear-thumbnail queues the delete + state1 (ptk/update (thumbnails/clear-thumbnail file-id object-id) + {:thumbnails {object-id uri}}) + ;; Step 2: remove-from-deletion-queue cancels the pending delete + state2 (ptk/update (thumbnails/remove-from-deletion-queue object-id) + state1)] + ;; Thumbnail was removed from :thumbnails map by clear-thumbnail + (t/is (nil? (get-in state2 [:thumbnails object-id]))) + ;; But the deletion queue entry was cancelled by remove-from-deletion-queue + (t/is (nil? (get-in state2 [deletion-queue-key object-id]))) + (t/is (empty? (get state2 deletion-queue-key))))) + + (t/deftest clear-thumbnail-batch-drains-queue + (let [file-id (uuid/next) + object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri1 "blob:http://localhost/thumb-1" + uri2 "blob:http://localhost/thumb-2" + ;; Build up the queue state manually (simulating accumulated clear-thumbnails) + state {deletion-queue-key {object-id1 uri1 object-id2 uri2}} + event (#'thumbnails/clear-thumbnail-batch) + result (ptk/update event state)] + ;; The queue is drained from application state + (t/is (empty? (get result deletion-queue-key))))) + + (t/deftest clear-thumbnail-batch-empty-queue-noop + (let [state {deletion-queue-key {}} + event (#'thumbnails/clear-thumbnail-batch) + result (ptk/update event state)] + ;; State unchanged when queue is already empty + (t/is (empty? (get result deletion-queue-key))) + (t/is (= state (dissoc result deletion-queue-key))))) + + (t/deftest assoc-thumbnail-adds-to-map + (let [object-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame") + uri "blob:http://localhost/new-thumb" + event (#'thumbnails/assoc-thumbnail object-id uri) + state {:thumbnails {}} + result (ptk/update event state)] + (t/is (= uri (get-in result [:thumbnails object-id]))))) + + (t/deftest duplicate-thumbnail-copies-entry + (let [old-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame") + new-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame") + uri "blob:http://localhost/dup-thumb" + event (thumbnails/duplicate-thumbnail old-id new-id) + state {:thumbnails {old-id uri}} + result (ptk/update event state)] + (t/is (= uri (get-in result [:thumbnails old-id]))) + (t/is (= uri (get-in result [:thumbnails new-id]))))) + + ;; --- Async WatchEvent tests --- + + (defn- make-obj-ids + "Helper to create n properly-formatted object-ids for a single file." + [file-id n] + (vec (repeatedly n #(thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")))) + + (t/deftest clear-thumbnail-batch-watch-calls-rpc-with-object-ids + (t/async done + (let [file-id (uuid/next) + oids (make-obj-ids file-id 3) + state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))} + event (#'thumbnails/clear-thumbnail-batch)] + (ptk/update event state) + (mock/with-mocks + {#'app.main.repo/cmd! mock/rpc-cmd!-mock + #'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [_] nil) + (fn [err] (t/is (nil? err)) (done')) + (fn [_] + (t/is (= 1 (count @mock/rpc-calls))) + (let [[{:keys [cmd params]}] @mock/rpc-calls] + (t/is (= :delete-file-object-thumbnails cmd)) + (t/is (= (vec oids) (:object-ids params)))) + (done'))))) + done)))) + + (t/deftest clear-thumbnail-batch-watch-partitions-large-batch + (t/async done + (let [file-id (uuid/next) + oids (make-obj-ids file-id 250) + state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))} + event (#'thumbnails/clear-thumbnail-batch)] + (ptk/update event state) + (mock/with-mocks + {#'app.main.repo/cmd! mock/rpc-cmd!-mock + #'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [_] nil) + (fn [err] (t/is (nil? err)) (done')) + (fn [_] + (t/is (= 2 (count @mock/rpc-calls))) + (let [[c1 c2] @mock/rpc-calls] + (t/is (= :delete-file-object-thumbnails (:cmd c1))) + (t/is (= :delete-file-object-thumbnails (:cmd c2))) + (t/is (= 200 (count (:object-ids (:params c1))))) + (t/is (= 50 (count (:object-ids (:params c2))))) + (t/is (= (set oids) + (set (concat (:object-ids (:params c1)) + (:object-ids (:params c2))))))) + (done'))))) + done)))) + + (t/deftest clear-thumbnail-batch-watch-revokes-blob-uris + (t/async done + (let [file-id (uuid/next) + oids (make-obj-ids file-id 2) + uris ["blob:http://localhost/thumb-1" + "blob:http://localhost/thumb-2"] + state {deletion-queue-key (zipmap oids uris)} + event (#'thumbnails/clear-thumbnail-batch)] + (ptk/update event state) + (mock/with-mocks + {#'app.main.repo/cmd! (fn [_ _] (rx/of nil)) + #'app.util.webapi/revoke-uri mock/revoke-uri-mock + #'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [_] nil) + (fn [err] (t/is (nil? err)) (done')) + (fn [_] + (t/is (= (set uris) (set @mock/revoked-uris))) + (done'))))) + done)))) + + (t/deftest clear-thumbnail-batch-watch-empty-queue-no-rpc + (t/async done + (let [event (#'thumbnails/clear-thumbnail-batch) + state {}] + (ptk/update event state) + (mock/with-mocks + {#'app.main.repo/cmd! mock/rpc-cmd!-mock + #'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [_] nil) + (fn [err] (t/is (nil? err)) (done')) + (fn [_] + (t/is (empty? @mock/rpc-calls)) + (done'))))) + done)))) + + (t/deftest clear-thumbnail-watch-emits-batch-after-debounce + (t/async done + (let [file-id (uuid/next) + object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame") + uri "blob:http://localhost/thumb" + state {:thumbnails {object-id uri}} + event (thumbnails/clear-thumbnail file-id object-id)] + (ptk/update event state) + (mock/with-mocks + {#'beicon.v2.core/timer mock/timer-mock} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [_] nil) + (fn [err] (t/is (nil? err)) (done')) + (fn [events] + (t/is (= 1 (count events))) + (t/is (ptk/event? (first events))) + (done'))))) + done))))) diff --git a/frontend/test/frontend_tests/helpers/mock.cljs b/frontend/test/frontend_tests/helpers/mock.cljs new file mode 100644 index 0000000000..bce234f4cf --- /dev/null +++ b/frontend/test/frontend_tests/helpers/mock.cljs @@ -0,0 +1,103 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns frontend-tests.helpers.mock + "Async-first mocking primitives for ClojureScript tests. + + Uses `with-redefs` — the standard CLJS mechanism for rebinding Vars + within a dynamic scope. Recording atoms (`rpc-calls`, `revoked-uris`) + persist across the entire test lifecycle, making captured data + inspectable regardless of whether callbacks fire synchronously or + asynchronously. + + The `with-mocks` helper wraps the lifecycle: + 1. Reset recording atoms + 2. Install mocks via `with-redefs` + 3. Execute `(test-fn inner-done)` + 4. `inner-done` calls `outer-done` (typically `cljs.test/async`'s done) + + Since all mock functions return synchronous `rx/of` observables, + callbacks always fire within the `with-redefs` body." + (:require + [beicon.v2.core :as rx])) + +;; ═══════════════════════════════════════════════════════════════ +;; Recording atoms +;; ═══════════════════════════════════════════════════════════════ + +(def rpc-calls + "Atom accumulating mocked `rp/cmd!` calls as `{:cmd kw :params map}`." + (atom [])) + +(def revoked-uris + "Atom accumulating URIs passed to `wapi/revoke-uri`." + (atom [])) + +;; ═══════════════════════════════════════════════════════════════ +;; Mock implementations +;; ═══════════════════════════════════════════════════════════════ + +(defn rpc-cmd!-mock + "Records [cmd params] in [[rpc-calls]], returns `(rx/of nil)`." + [cmd params] + (swap! rpc-calls conj {:cmd cmd :params params}) + (rx/of nil)) + +(defn revoke-uri-mock + "Records `uri` in [[revoked-uris]]." + [uri] + (swap! revoked-uris conj uri)) + +(defn schedule-on-idle-mock + "Calls `f` immediately instead of deferring to the idle queue." + [f] + (f)) + +(defn timer-mock + "Returns `(rx/of :immediate)` so debounce timers fire instantly + during tests." + [_ms] + (rx/of :immediate)) + +;; ═══════════════════════════════════════════════════════════════ +;; Lifecycle +;; ═══════════════════════════════════════════════════════════════ + +(defn reset! + "Clear all recording atoms. Called automatically by [[with-mocks]]." + [] + (reset! rpc-calls []) + (reset! revoked-uris [])) + +;; ═══════════════════════════════════════════════════════════════ +;; Public API +;; ═══════════════════════════════════════════════════════════════ + +(defn with-mocks + "Resets recording atoms, installs `mocks` via `with-redefs`, then + calls `(test-fn inner-done)`. + + `mocks` is a map of `Var → mock-fn` (e.g. `{#'rp/cmd! mock-fn}`). + `inner-done` tears down the `with-redefs` (by returning) and calls + `outer-done` (the `cljs.test/async` `done` callback). + + Example: + + (t/deftest my-async-test + (t/async done + (mock/with-mocks + {#'rp/cmd! mock/rpc-cmd!-mock} + (fn [done'] + (->> (some-async-flow) + (rx/subs! + (fn [v] ...) + (fn [err] (done')) + (fn [] (done'))))))))" + [mocks test-fn outer-done] + (reset!) + (apply with-redefs (mapcat identity mocks) + (test-fn (fn inner-done [] + (outer-done))))) diff --git a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs index 186fa3071c..b1c19573c7 100644 --- a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs +++ b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs @@ -562,3 +562,74 @@ ;; Total = 1 + 2 + 3 + 4 + 2 + 3 + 4 + 4 + 3 + 2 + 1 = 29 (t/is (= (count (:objects page')) 29))))))))) +;; --------------------------------------------------------------------------- +;; Tests for issue-14302 +;; Pasting a board with text shapes must clear stale position-data so the +;; workspace can remeasure the text and avoid incorrect line breaks. +;; --------------------------------------------------------------------------- + +(defn- setup-board-with-texts + "Two-page file. Page 1 has a board with two text shapes that carry stale + position-data (simulating a shape that was already laid out on a + different page). Page 2 is empty. Current page is page-1." + [] + (let [stale-pd [{:x 999 :y 999 :width 999 :height 20 :fills [] :text "stale"}]] + (-> (cthf/sample-file :file1 :page-label :page-1) + (ctho/add-frame :frame-1 :name "board-with-texts") + (cths/add-sample-shape :text-a :type :text :parent-label :frame-1 :name "text-a") + (cths/update-shape :text-a :position-data stale-pd) + (cths/add-sample-shape :text-b :type :text :parent-label :frame-1 :name "text-b") + (cths/update-shape :text-b :position-data stale-pd) + (cthf/add-sample-page :page-2) + (cthf/switch-to-page :page-1)))) + +(t/deftest paste-to-empty-page-clears-text-position-data + "Regression for issue-14302: copying a board with text shapes and pasting + onto an empty page produced incorrect line breaks because stale + position-data from the source page was preserved." + (t/async done + (with-redefs [uuid/next cthi/next-uuid] + (let [file (setup-board-with-texts) + store (ths/setup-store file) + ;; copy-paste-shape handles initialize-page + select-shape (needed + ;; for calculate-paste-position) + paste-shapes + return to page-1 + events (copy-paste-shape :frame-1 file + :target-page-label :page-2 + :target-container-id uuid/zero)] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + page-2 (cthf/get-page file' :page-2) + pasted-texts (->> (vals (:objects page-2)) + (filter #(= :text (:type %))))] + (t/is (= 2 (count pasted-texts)) + "Both text shapes are pasted onto the empty page") + (t/is (every? #(nil? (:position-data %)) pasted-texts) + "Pasted text shapes have nil position-data so they get remeasured")))))))) + +(t/deftest paste-to-same-page-clears-text-position-data + "Position-data is stripped on paste regardless of destination page." + (t/async done + (with-redefs [uuid/next cthi/next-uuid] + (let [file (setup-board-with-texts) + store (ths/setup-store file) + events (copy-paste-shape :frame-1 file + :target-container-id uuid/zero)] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + page-1' (cthf/get-page file' :page-1) + ;; Pasted copies have IDs not registered in idmap — they show + ;; up as "<no-label …>" strings from cthi/label. + pasted-texts (->> (vals (:objects page-1')) + (filter #(= :text (:type %))) + (remove #(keyword? (cthi/label (:id %)))))] + (t/is (= 2 (count pasted-texts)) + "Two new text shapes are pasted onto the same page") + (t/is (every? #(nil? (:position-data %)) pasted-texts) + "Pasted text shapes have nil position-data")))))))) + diff --git a/frontend/test/frontend_tests/plugins/page_test.cljs b/frontend/test/frontend_tests/plugins/page_test.cljs new file mode 100644 index 0000000000..496465ff10 --- /dev/null +++ b/frontend/test/frontend_tests/plugins/page_test.cljs @@ -0,0 +1,94 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.plugins.page-test + (:require + [app.common.test-helpers.files :as cthf] + [app.main.data.workspace.pages :as dwpg] + [app.main.store :as st] + [app.plugins.api :as api] + [app.util.object :as obj] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.state :as ths] + [potok.v2.core :as ptk])) + +(defn- setup + "Creates a file with two pages (page1 as current) and a plugin context." + [] + (let [file (-> (cthf/sample-file :file1 :page-label :page1) + (cthf/add-sample-page :page2) + (cthf/switch-to-page :page1)) + store (ths/setup-store file) + _ (set! st/state store) + _ (set! st/stream (ptk/input-stream store)) + context (api/create-context "00000000-0000-0000-0000-000000000000")] + {:file file :store store :context context})) + +(defn- mock-page-initialized + "Simulates the two effects of initialize-page* without routing: + updates current-page-id in state, then emits the public ::dwpg/initialized event." + [store page-id] + (ptk/emit! store #(assoc % :current-page-id page-id)) + (ptk/emit! store (ptk/data-event ::dwpg/initialized page-id))) + +(t/deftest test-open-page-returns-promise + (let [{:keys [context]} (setup) + ^js pages (.. context -currentFile -pages) + ^js page2 (aget pages 1)] + (t/is (instance? js/Promise (.openPage context page2))))) + +(t/deftest test-open-page-new-window-returns-promise + (let [{:keys [context]} (setup) + ^js pages (.. context -currentFile -pages) + ^js page2 (aget pages 1)] + (t/is (instance? js/Promise (.openPage context page2 true))))) + +(t/deftest test-open-page-invalid-arg-returns-nil + (let [{:keys [context]} (setup)] + (t/is (nil? (.openPage context "not-a-page"))))) + +(t/deftest test-open-page-resolves-when-page-changes + (t/async done + (let [{:keys [store context]} (setup) + ^js pages (.. context -currentFile -pages) + ^js page2 (aget pages 1) + page2-id (obj/get page2 "$id")] + + (-> (.openPage context page2) + (.then (fn [_] + (t/is (= (:current-page-id @store) page2-id)) + (done)))) + + (mock-page-initialized store page2-id)))) + +(t/deftest test-open-page-does-not-resolve-for-wrong-page + ;; Promise should not resolve when a different page is initialized + (t/async done + (let [{:keys [store context]} (setup) + ^js pages (.. context -currentFile -pages) + ^js page1 (aget pages 0) + ^js page2 (aget pages 1) + page1-id (obj/get page1 "$id") + page2-id (obj/get page2 "$id") + resolved? (atom false)] + + (-> (.openPage context page2) + (.then (fn [_] (reset! resolved? true)))) + + ;; Initialize page1 (wrong page) — promise should not resolve + (mock-page-initialized store page1-id) + + ;; Give microtasks a chance to run, then verify promise is still pending + (js/setTimeout + (fn [] + (t/is (not @resolved?)) + ;; Now initialize the correct page and confirm it resolves + (-> (.openPage context page2) + (.then (fn [_] + (t/is (= (:current-page-id @store) page2-id)) + (done)))) + (mock-page-initialized store page2-id)) + 0)))) diff --git a/frontend/test/frontend_tests/render_wasm/process_objects_test.cljs b/frontend/test/frontend_tests/render_wasm/process_objects_test.cljs new file mode 100644 index 0000000000..e20aa2e1bc --- /dev/null +++ b/frontend/test/frontend_tests/render_wasm/process_objects_test.cljs @@ -0,0 +1,110 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.render-wasm.process-objects-test + "Unit tests for wasm.api/process-objects. + + The key invariant: process-objects must call set-object for every shape + and forward ALL shapes to a single process-pending invocation. + + Without this batching the async font-load callback only covers the shape + that triggered the font fetch. Subsequent text shapes that share the same + font URL get no callback (fetch-font returns nil when the URL is already + in :fetching) and are permanently stuck with fallback-font layout metrics." + (:require + [app.render-wasm.api :as wasm.api] + [beicon.v2.core :as rx] + [cljs.test :as t :include-macros true])) + +;; --------------------------------------------------------------------------- +;; Helpers +;; --------------------------------------------------------------------------- + +(defn- make-shape [type] + {:id (random-uuid) :type type}) + +(defn- with-mocks* + "Temporarily replaces wasm.api/set-object and wasm.api/process-pending, + calls (thunk), then restores the originals." + [mock-set-object mock-process-pending thunk] + (let [orig-set-object wasm.api/set-object + orig-process-pending wasm.api/process-pending] + (set! wasm.api/set-object mock-set-object) + (set! wasm.api/process-pending mock-process-pending) + (try + (thunk) + (finally + (set! wasm.api/set-object orig-set-object) + (set! wasm.api/process-pending orig-process-pending))))) + +;; --------------------------------------------------------------------------- +;; Tests +;; --------------------------------------------------------------------------- + +(t/deftest process-objects-calls-set-object-for-every-shape + "Each shape in the input must go through set-object exactly once." + (let [shapes [(make-shape :text) (make-shape :text) (make-shape :rect)] + visited-ids (atom []) + mock-set (fn [s] (swap! visited-ids conj (:id s)) {:thumbnails [] :full []}) + mock-pend (fn [_sh _t _f _cb] nil)] + + (with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes)) + + (t/is (= (mapv :id shapes) @visited-ids) + "set-object called once per shape in order"))) + +(t/deftest process-objects-calls-process-pending-once-with-all-shapes + "process-pending must receive ALL shapes in a single call. + This is the invariant that makes the async font-load callback update text + layouts for every text shape, not just the first one that triggered the + font fetch." + (let [shapes [(make-shape :text) (make-shape :text)] + captured (atom nil) + mock-set (fn [_s] {:thumbnails [] :full []}) + mock-pend (fn [sh t f cb] (reset! captured {:shapes sh :thumbnails t :full f :cb cb}))] + + (with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes)) + + (t/is (some? @captured) + "process-pending was called") + (t/is (= 2 (count (:shapes @captured))) + "process-pending received all shapes") + (t/is (= (set (map :id shapes)) + (set (map :id (:shapes @captured)))) + "process-pending received the correct shape objects"))) + +(t/deftest process-objects-accumulates-callbacks-across-shapes + "Pending font/image callbacks from all shapes must be merged into the single + process-pending call. This covers the deduplication scenario: when a second + text shape shares the same font (fetch-font returns nil because the URL is + already in :fetching), the callback from the first shape still covers the + second shape because both are in the :shapes list passed to process-pending." + (let [shape-a (make-shape :text) + shape-b (make-shape :text) + shapes [shape-a shape-b] + font-cb (fn [] (rx/of true)) + captured (atom nil) + + ;; Simulate deduplication: shape-a triggers a real font fetch, + ;; shape-b gets an empty pending list (same URL already in :fetching). + mock-set + (fn [s] + (if (= (:id s) (:id shape-a)) + {:thumbnails [{:key "font-url" :callback font-cb}] :full []} + {:thumbnails [] :full []})) + + mock-pend + (fn [sh t f _cb] (reset! captured {:shapes sh :thumbnails t :full f}))] + + (with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes)) + + ;; The single font callback from shape-a is present... + (t/is (= 1 (count (:thumbnails @captured))) + "The font callback from shape-a is forwarded") + ;; ...and BOTH shapes are in the list, so when the font loads and + ;; process-pending fires update-text-layouts, it covers shape-b too. + (t/is (= 2 (count (:shapes @captured))) + "Both shapes are in process-pending so font-load covers all of them"))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index d7048409a8..da17eeed40 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -11,6 +11,7 @@ [frontend-tests.data.uploads-test] [frontend-tests.data.viewer-test] [frontend-tests.data.workspace-colors-test] + [frontend-tests.data.workspace-mcp-test] [frontend-tests.data.workspace-media-test] [frontend-tests.data.workspace-shortcuts-test] [frontend-tests.data.workspace-texts-test] @@ -25,9 +26,11 @@ [frontend-tests.logic.pasting-in-containers-test] [frontend-tests.main-errors-test] [frontend-tests.plugins.context-shapes-test] + [frontend-tests.plugins.page-test] [frontend-tests.plugins.parser-test] [frontend-tests.plugins.tokens-test] [frontend-tests.plugins.utils-test] + [frontend-tests.render-wasm.process-objects-test] [frontend-tests.svg-fills-test] [frontend-tests.tokens.import-export-test] [frontend-tests.tokens.logic.token-actions-test] @@ -61,6 +64,7 @@ frontend-tests.data.uploads-test frontend-tests.data.viewer-test frontend-tests.data.workspace-colors-test + frontend-tests.data.workspace-mcp-test frontend-tests.data.workspace-media-test frontend-tests.data.workspace-shortcuts-test frontend-tests.data.workspace-texts-test @@ -73,6 +77,7 @@ frontend-tests.logic.groups-test frontend-tests.logic.pasting-in-containers-test frontend-tests.plugins.context-shapes-test + frontend-tests.plugins.page-test frontend-tests.plugins.parser-test frontend-tests.plugins.tokens-test frontend-tests.plugins.utils-test @@ -85,6 +90,7 @@ frontend-tests.tokens.token-errors-test frontend-tests.tokens.workspace-tokens-remap-test frontend-tests.ui.ds-controls-numeric-input-test + frontend-tests.render-wasm.process-objects-test frontend-tests.util-object-test frontend-tests.util-range-tree-test frontend-tests.util-simple-math-test diff --git a/frontend/text-editor/package.json b/frontend/text-editor/package.json index df2345252c..a1af546488 100644 --- a/frontend/text-editor/package.json +++ b/frontend/text-editor/package.json @@ -15,18 +15,18 @@ "test:watch:e2e": "vitest --browser" }, "devDependencies": { - "@playwright/test": "^1.45.1", - "@types/node": "^25.0.3", - "@vitest/browser": "^1.6.0", - "@vitest/coverage-v8": "^1.6.0", - "@vitest/ui": "^1.6.0", - "esbuild": "^0.27.2", - "jsdom": "^27.4.0", - "canvas": "^3.2.1", - "playwright": "^1.45.1", - "prettier": "^3.7.4", - "vite": "^5.3.1", - "vitest": "^1.6.0" + "@playwright/test": "^1.61.0", + "@types/node": "^25.9.2", + "@vitest/browser": "^4.1.9", + "@vitest/coverage-v8": "^4.1.9", + "@vitest/ui": "^4.1.9", + "canvas": "^3.2.3", + "esbuild": "^0.28.0", + "jsdom": "^29.1.1", + "playwright": "^1.61.0", + "prettier": "^3.8.4", + "vite": "^8.0.16", + "vitest": "^4.1.9" }, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268" + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620" } diff --git a/frontend/text-editor/src/editor/content/dom/Style.test.js b/frontend/text-editor/src/editor/content/dom/Style.test.js index 325ccdbc92..8e401f1cde 100644 --- a/frontend/text-editor/src/editor/content/dom/Style.test.js +++ b/frontend/text-editor/src/editor/content/dom/Style.test.js @@ -17,14 +17,22 @@ describe("Style", () => { test("setStyles should apply multiple styles to an element using an Object", () => { const element = document.createElement("div"); - setStyles(element, [["display"]], { - "text-decoration": "none", - "font-size": "32px", - display: "none", - }); - expect(element.style.display).toBe(""); - expect(element.style.fontSize).toBe(""); - expect(element.style.textDecoration).toBe(""); + setStyles( + element, + [ + ["display"], + ["font-size", "px"], + ["text-decoration"], + ], + { + "text-decoration": "none", + "font-size": "32", + display: "none", + }, + ); + expect(element.style.display).toBe("none"); + expect(element.style.fontSize).toBe("32px"); + expect(element.style.textDecoration).toBe("none"); }); test("setStyles should apply multiple styles to an element using a CSSStyleDeclaration", () => { @@ -32,13 +40,13 @@ describe("Style", () => { setStyles(a, [["display"]], { display: "none", }); - expect(a.style.display).toBe(""); + expect(a.style.display).toBe("none"); expect(a.style.fontSize).toBe(""); expect(a.style.textDecoration).toBe(""); const b = document.createElement("div"); setStyles(b, [["display"]], a.style); - expect(b.style.display).toBe(""); + expect(b.style.display).toBe("none"); expect(b.style.fontSize).toBe(""); expect(b.style.textDecoration).toBe(""); }); diff --git a/frontend/text-editor/vite.config.js b/frontend/text-editor/vite.config.js index bce37c7715..366661f6bd 100644 --- a/frontend/text-editor/vite.config.js +++ b/frontend/text-editor/vite.config.js @@ -2,6 +2,7 @@ import path from "node:path"; import fs from "node:fs/promises"; import { defineConfig } from "vite"; import { coverageConfigDefaults } from "vitest/config"; +import { playwright } from "@vitest/browser-playwright"; async function waitFor(timeInMillis) { return new Promise((resolve) => setTimeout((_) => resolve(), timeInMillis)); @@ -69,11 +70,7 @@ export default defineConfig({ enabled: true, exclude: ["main.js", "**/scripts/**", ...coverageConfigDefaults.exclude], }, - poolOptions: { - threads: { - singleThread: true, - }, - }, + singleThread: true, environmentOptions: { jsdom: { resources: "usable", @@ -81,7 +78,7 @@ export default defineConfig({ }, browser: { name: "chromium", - provider: "playwright", + provider: playwright(), }, exclude: ["main.js", "**/scripts/**", "**/node_modules/**", "**/dist/**"], }, diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 64dc9c4a41..a3559f9c1d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4873,7 +4873,7 @@ msgstr "What's the size of your company?" #: src/app/main/ui/onboarding/questions.cljs:269 msgid "onboarding.questions.step3.title" -msgstr "Tell us about your job" +msgstr "What is your company size?" #: src/app/main/ui/onboarding/questions.cljs:344 msgid "onboarding.questions.step4.title" @@ -4884,7 +4884,25 @@ msgstr "Where would you like to get started?" msgid "onboarding.questions.step5.title" msgstr "How did you hear about Penpot?" -#: src/app/main/ui/onboarding/questions.cljs:232 +msgid "onboarding.questions.team-size.just-me" +msgstr "Just me" + +msgid "onboarding.questions.team-size.2-100" +msgstr "2 - 100" + +msgid "onboarding.questions.team-size.101-500" +msgstr "101 - 500" + +msgid "onboarding.questions.team-size.501-1000" +msgstr "501 - 1,000" + +msgid "onboarding.questions.team-size.1001-5000" +msgstr "1,001 - 5,000" + +msgid "onboarding.questions.team-size.more-than-5001" +msgstr "5,001+" + +#: src/app/main/ui/onboarding/questions.cljs:233 msgid "onboarding.questions.team-size.11-30" msgstr "11-30" @@ -6980,8 +6998,8 @@ msgid "workspace.libraries.colors.add-library-color" msgstr "Add color to library" #: src/app/main/ui/workspace/colorpicker/libraries.cljs -msgid "workspace.libraries.colors.show-color-palette" -msgstr "Show color palette" +msgid "workspace.libraries.colors.toggle-color-palette" +msgstr "Toggle color palette" #: src/app/main/ui/workspace/colorpicker/libraries.cljs msgid "workspace.libraries.colors.empty-recent-colors" @@ -9633,6 +9651,10 @@ msgstr "Assets" msgid "workspace.toolbar.color-palette" msgstr "Color Palette (%s)" +#: src/app/main/ui/workspace/palette.cljs +msgid "workspace.toolbar.palette-bar" +msgstr "Palette bar" + #: src/app/main/ui/workspace/right_header.cljs:214, src/app/main/ui/workspace/right_header.cljs:215 msgid "workspace.toolbar.comments" msgstr "Comments (%s)" @@ -9662,7 +9684,16 @@ msgstr "Create board. Click and drag to define its size. (%s)" msgid "workspace.toolbar.image" msgstr "Image (%s)" -#: src/app/main/ui/workspace/top_toolbar.cljs:139, src/app/main/ui/workspace/top_toolbar.cljs:140 +msgid "workspace.toolbar.mcp" +msgstr "MCP" + +msgid "workspace.toolbar.mcp-connected" +msgstr "MCP connected" + +msgid "workspace.toolbar.mcp-connect-here" +msgstr "Connect here" + +#: src/app/main/ui/workspace/top_toolbar.cljs:143 msgid "workspace.toolbar.move" msgstr "Move (%s)" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9050f78a21..35393cf0f7 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4742,7 +4742,7 @@ msgstr "¿Cuál es el tamaño de tu empresa?" #: src/app/main/ui/onboarding/questions.cljs:269 msgid "onboarding.questions.step3.title" -msgstr "Háblanos de tu trabajo" +msgstr "¿Cuál es el tamaño de tu empresa?" #: src/app/main/ui/onboarding/questions.cljs:344 msgid "onboarding.questions.step4.title" @@ -4753,7 +4753,25 @@ msgstr "¿Por dónde te apetecería empezar?" msgid "onboarding.questions.step5.title" msgstr "¿Cómo nos has descubierto?" -#: src/app/main/ui/onboarding/questions.cljs:232 +msgid "onboarding.questions.team-size.just-me" +msgstr "Sólo yo" + +msgid "onboarding.questions.team-size.2-100" +msgstr "2 - 100" + +msgid "onboarding.questions.team-size.101-500" +msgstr "101 - 500" + +msgid "onboarding.questions.team-size.501-1000" +msgstr "501 - 1000" + +msgid "onboarding.questions.team-size.1001-5000" +msgstr "1001 - 5000" + +msgid "onboarding.questions.team-size.more-than-5001" +msgstr "5001+" + +#: src/app/main/ui/onboarding/questions.cljs:233 msgid "onboarding.questions.team-size.11-30" msgstr "11-30" @@ -6812,8 +6830,8 @@ msgid "workspace.libraries.colors.add-library-color" msgstr "Añadir color a la biblioteca del archivo" #: src/app/main/ui/workspace/colorpicker/libraries.cljs -msgid "workspace.libraries.colors.show-color-palette" -msgstr "Mostrar paleta de colores" +msgid "workspace.libraries.colors.toggle-color-palette" +msgstr "Mostrar/Ocultar paleta de colores" #: src/app/main/ui/workspace/colorpicker/libraries.cljs msgid "workspace.libraries.colors.empty-recent-colors" @@ -9306,6 +9324,10 @@ msgstr "Recursos" msgid "workspace.toolbar.color-palette" msgstr "Paleta de colores (%s)" +#: src/app/main/ui/workspace/palette.cljs +msgid "workspace.toolbar.palette-bar" +msgstr "Barra de paletas" + #: src/app/main/ui/workspace/right_header.cljs:214, src/app/main/ui/workspace/right_header.cljs:215 msgid "workspace.toolbar.comments" msgstr "Comentarios (%s)" @@ -9335,7 +9357,16 @@ msgstr "Crear tablero. Click y arrastrar para definir el tamaño. (%s)" msgid "workspace.toolbar.image" msgstr "Imagen (%s)" -#: src/app/main/ui/workspace/top_toolbar.cljs:139, src/app/main/ui/workspace/top_toolbar.cljs:140 +msgid "workspace.toolbar.mcp" +msgstr "MCP" + +msgid "workspace.toolbar.mcp-connected" +msgstr "MCP conectado" + +msgid "workspace.toolbar.mcp-connect-here" +msgstr "Conectar aquí" + +#: src/app/main/ui/workspace/top_toolbar.cljs:143 msgid "workspace.toolbar.move" msgstr "Mover (%s)" diff --git a/library/package.json b/library/package.json index e8544cc086..ea08e64218 100644 --- a/library/package.json +++ b/library/package.json @@ -3,7 +3,7 @@ "version": "1.2.0-RC1", "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "type": "module", "repository": { "type": "git", diff --git a/manage.sh b/manage.sh index 487486f8ca..9063f0f7dd 100755 --- a/manage.sh +++ b/manage.sh @@ -59,7 +59,7 @@ PENPOT_PORT_BASE_MDTS=${MDTS_EXTERNAL_PORT:?missing in defaults.env} export CURRENT_USER_ID=$(id -u); export CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD); -export IMAGEMAGICK_VERSION=7.1.2-13 +export IMAGEMAGICK_VERSION=7.1.2-24 # Safe directory to avoid ownership errors with Git git config --global --add safe.directory /home/penpot/penpot || true @@ -1281,16 +1281,17 @@ function usage { echo "- build-frontend-bundle Build frontend bundle" echo "- build-backend-bundle Build backend bundle." echo "- build-exporter-bundle Build exporter bundle." - echo "- build-storybook-bundle Build storybook bundle." echo "- build-mcp-bundle Build mcp bundle." + echo "- build-storybook-bundle Build storybook bundle." echo "- build-docs-bundle Build docs bundle." echo "" - echo "- build-docker-images Build all docker images (frontend, backend and exporter)." + echo "- build-docker-images Build all docker images (frontend, backend, exporter, mcp and storybook)." echo "- build-frontend-docker-image Build frontend docker images." echo "- build-backend-docker-image Build backend docker images." echo "- build-exporter-docker-image Build exporter docker images." echo "- build-mcp-docker-image Build exporter docker images." echo "- build-storybook-docker-image Build storybook docker images." + echo "- build-imagemagick-docker-image Build imagemagic docker images." echo "" echo "- version Show penpot's version." } @@ -1369,11 +1370,6 @@ case $1 in build-docs-bundle; ;; - build-imagemagick-docker-image) - shift; - build-imagemagick-docker-image $@; - ;; - build-docker-images) build-frontend-docker-image build-backend-docker-image @@ -1402,6 +1398,11 @@ case $1 in build-storybook-docker-image ;; + build-imagemagick-docker-image) + shift; + build-imagemagick-docker-image $@; + ;; + *) usage ;; diff --git a/mcp/package.json b/mcp/package.json index 2318a80951..ff37ae65c4 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,7 +1,8 @@ { "name": "@penpot/mcp", - "version": "2.16.0-rc.1.206", + "version": "2.16.0", "description": "MCP server for Penpot integration", + "license": "MPL-2.0", "bin": { "penpot-mcp": "./bin/mcp-local.js" }, @@ -20,9 +21,9 @@ "type": "git", "url": "https://github.com/penpot/penpot.git" }, - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "devDependencies": { - "concurrently": "^9.2.1", - "prettier": "^3.0.0" + "concurrently": "^10.0.3", + "prettier": "^3.8.4" } } diff --git a/mcp/packages/plugin/package.json b/mcp/packages/plugin/package.json index 0ccf276181..9dddf112fd 100644 --- a/mcp/packages/plugin/package.json +++ b/mcp/packages/plugin/package.json @@ -7,6 +7,7 @@ "start": "vite build --watch --config vite.config.ts", "start:multi-user": "pnpm run start", "build": "tsc && vite build --config vite.release.config.ts", + "test": "node --experimental-strip-types --test src/*.test.ts", "types:check": "tsc --noEmit", "clean": "rm -rf dist/" }, diff --git a/mcp/packages/plugin/src/ErrorUtils.test.ts b/mcp/packages/plugin/src/ErrorUtils.test.ts new file mode 100644 index 0000000000..e88b444463 --- /dev/null +++ b/mcp/packages/plugin/src/ErrorUtils.test.ts @@ -0,0 +1,63 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { formatTaskError } from "./ErrorUtils.ts"; + +test("includes ClojureScript exception data in the task error message", () => { + const keyword = (name: string) => ({ name, fqn: name }); + const entries = [ + { key: keyword("type"), val: keyword("validation") }, + { key: keyword("code"), val: keyword("request-body-too-large") }, + { key: keyword("status"), val: 413 }, + ]; + const data = { + *[Symbol.iterator]() { + yield* entries; + }, + }; + const error = Object.assign(new Error("http error"), { data }); + + assert.equal(formatTaskError(error), "http error (type: validation, code: request-body-too-large, status: 413)"); +}); + +test("falls back to the printed representation when map internals are renamed by the Closure compiler", () => { + // In release builds MapEntry/Keyword field names are minified, so entry + // extraction finds nothing; only toString keeps the readable CLJS form. + const entries = [{ a7: { Eb: "type" }, gb: { Eb: "validation" } }]; + const data = { + *[Symbol.iterator]() { + yield* entries; + }, + toString: () => "{:type :validation, :code :request-body-too-large}", + }; + const error = Object.assign(new Error("http error"), { data }); + + assert.equal(formatTaskError(error), "http error ({:type :validation, :code :request-body-too-large})"); +}); + +test("formats plain object data and nested printable values", () => { + const data = { + status: 413, + uri: "https://example.test/api/export", + detail: { toString: () => "{:code :too-large}" }, + }; + const error = Object.assign(new Error("http error"), { data }); + + assert.equal( + formatTaskError(error), + "http error (status: 413, uri: https://example.test/api/export, detail: {:code :too-large})" + ); +}); + +test("keeps string values verbatim", () => { + const error = Object.assign(new Error("http error"), { data: { hint: ":not-a-keyword" } }); + + assert.equal(formatTaskError(error), "http error (hint: :not-a-keyword)"); +}); + +test("returns the original message when no structured data is available", () => { + assert.equal(formatTaskError(new Error("export timed out")), "export timed out"); +}); + +test("formats non-Error values", () => { + assert.equal(formatTaskError("connection closed"), "connection closed"); +}); diff --git a/mcp/packages/plugin/src/ErrorUtils.ts b/mcp/packages/plugin/src/ErrorUtils.ts new file mode 100644 index 0000000000..c898037b7d --- /dev/null +++ b/mcp/packages/plugin/src/ErrorUtils.ts @@ -0,0 +1,145 @@ +type ErrorWithData = Error & { + data?: unknown; +}; + +/** + * Produces a useful task error message, including structured ClojureScript exception data. + * + * @param error - the value thrown while handling a plugin task + * @returns A human-readable error message + */ +export function formatTaskError(error: unknown): string { + if (!(error instanceof Error)) { + return String(error); + } + + const details = formatErrorData((error as ErrorWithData).data); + return details ? `${error.message} (${details})` : error.message; +} + +/** + * Formats plain objects and iterable ClojureScript maps as key-value pairs. + * + * Falls back to the value's printed representation: in release builds the + * Closure compiler renames ClojureScript internals (MapEntry `key`/`val`, + * Keyword `fqn`), so entry extraction can come up empty even though `toString` + * still prints the data readably (e.g. `{:type :validation}`). + * + * @param data - structured exception data + * @returns Formatted exception details, or an empty string when unavailable + */ +function formatErrorData(data: unknown): string { + const entries = getEntries(data); + if (entries.length > 0) { + return entries.map(([key, value]) => `${formatKey(key)}: ${formatValue(value)}`).join(", "); + } + + return printedForm(data); +} + +/** + * Returns the value's own printed representation, or an empty string when it + * only has the default `Object.prototype.toString` one. + */ +function printedForm(value: unknown): string { + if (!value || typeof value !== "object") { + return ""; + } + + try { + const text = String(value); + return text === "[object Object]" ? "" : text; + } catch { + return ""; + } +} + +/** + * Extracts entries from JavaScript objects or ClojureScript map-like values. + */ +function getEntries(data: unknown): Array<[unknown, unknown]> { + if (!data || typeof data !== "object") { + return []; + } + + if (Symbol.iterator in data) { + try { + return Array.from(data as Iterable<unknown>).flatMap((entry) => { + if (Array.isArray(entry) && entry.length >= 2) { + return [[entry[0], entry[1]]]; + } + + if (entry && typeof entry === "object") { + const mapEntry = entry as { key?: unknown; val?: unknown }; + if ("key" in mapEntry && "val" in mapEntry) { + return [[mapEntry.key, mapEntry.val]]; + } + + if (Symbol.iterator in entry) { + const pair = Array.from(entry as Iterable<unknown>); + return pair.length >= 2 ? [[pair[0], pair[1]]] : []; + } + } + + return []; + }); + } catch { + // fall through to ordinary object properties + } + } + + return Object.entries(data); +} + +/** + * Formats ClojureScript keywords without their leading colon. + */ +function formatKey(key: unknown): string { + if (key && typeof key === "object") { + const keyword = key as { fqn?: unknown; name?: unknown }; + if (typeof keyword.fqn === "string") { + return keyword.fqn; + } + if (typeof keyword.name === "string") { + return keyword.name; + } + } + + return String(key).replace(/^:/, ""); +} + +/** + * Formats detail values while keeping nested data readable. + */ +function formatValue(value: unknown): string { + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return String(value); + } + + if (value && typeof value === "object") { + const keyword = value as { fqn?: unknown; name?: unknown }; + if (typeof keyword.fqn === "string") { + return keyword.fqn; + } + if (typeof keyword.name === "string") { + return keyword.name; + } + + if (!Array.isArray(value)) { + const printed = printedForm(value); + if (printed) { + return printed.replace(/^:/, ""); + } + } + } + + if (value === null || value === undefined) { + return String(value); + } + + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} diff --git a/mcp/packages/plugin/src/plugin.ts b/mcp/packages/plugin/src/plugin.ts index 34e0349e96..502116707f 100644 --- a/mcp/packages/plugin/src/plugin.ts +++ b/mcp/packages/plugin/src/plugin.ts @@ -1,5 +1,6 @@ import { ExecuteCodeTaskHandler } from "./task-handlers/ExecuteCodeTaskHandler"; import { Task, TaskHandler } from "./TaskHandler"; +import { formatTaskError } from "./ErrorUtils"; /** * indicates whether the plugin is running in an environment with the Penpot-integrated remote MCP server @@ -97,8 +98,7 @@ async function handlePluginTaskRequest(request: { id: string; task: string; para console.log("Task handled successfully:", task); } catch (error) { console.error("Error handling task:", error); - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - task.sendError(`Error handling task: ${errorMessage}`); + task.sendError(`Error handling task: ${formatTaskError(error)}`); } } else { console.error("Unknown plugin task:", request.task); diff --git a/mcp/packages/plugin/tsconfig.json b/mcp/packages/plugin/tsconfig.json index 0db6d8a2c6..de5c7b8ff9 100644 --- a/mcp/packages/plugin/tsconfig.json +++ b/mcp/packages/plugin/tsconfig.json @@ -20,5 +20,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/*.test.ts"] } diff --git a/mcp/packages/server/data/api_types.yml b/mcp/packages/server/data/api_types.yml index 54b4100e4a..de532d4eb9 100644 --- a/mcp/packages/server/data/api_types.yml +++ b/mcp/packages/server/data/api_types.yml @@ -15,7 +15,7 @@ Penpot: ) => void; size: { width: number; height: number } | null; resize: (width: number, height: number) => void; - sendMessage: (message: unknown) => void; + sendMessage: (message: unknown, throwOnError?: boolean) => void; onMessage: <T>(callback: (message: T) => void) => void; }; utils: ContextUtils; @@ -105,7 +105,7 @@ Penpot: ) => void; size: { width: number; height: number } | null; resize: (width: number, height: number) => void; - sendMessage: (message: unknown) => void; + sendMessage: (message: unknown, throwOnError?: boolean) => void; onMessage: <T>(callback: (message: T) => void) => void; } ``` @@ -130,7 +130,7 @@ Penpot: ``` penpot.ui.resize(300, 400); ``` - * sendMessage: (message: unknown) => void + * sendMessage: (message: unknown, throwOnError?: boolean) => void Sends a message to the plugin UI. @@ -4592,23 +4592,23 @@ CommonLayout: ``` interface CommonLayout { - alignItems?: "center" | "start" | "end" | "stretch"; + alignItems?: "end" | "start" | "center" | "stretch"; alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" | "space-evenly"; - justifyItems?: "center" + justifyItems?: "end" | "start" - | "end" + | "center" | "stretch"; justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -4638,7 +4638,7 @@ CommonLayout: Properties: alignItems: |- ``` - alignItems?: "center" | "start" | "end" | "stretch" + alignItems?: "end" | "start" | "center" | "stretch" ``` The `alignItems` property specifies the default alignment for items inside the container. @@ -4651,9 +4651,9 @@ CommonLayout: alignContent: |- ``` alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -4672,7 +4672,7 @@ CommonLayout: * 'stretch': Content is stretched to fill the container. justifyItems: |- ``` - justifyItems?: "center" | "start" | "end" | "stretch" + justifyItems?: "end" | "start" | "center" | "stretch" ``` The `justifyItems` property specifies the default justification for items inside the container. @@ -4685,9 +4685,9 @@ CommonLayout: justifyContent: |- ``` justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7298,23 +7298,23 @@ FlexLayout: ``` interface FlexLayout { - alignItems?: "center" | "start" | "end" | "stretch"; + alignItems?: "end" | "start" | "center" | "stretch"; alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" | "space-evenly"; - justifyItems?: "center" + justifyItems?: "end" | "start" - | "end" + | "center" | "stretch"; justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7348,7 +7348,7 @@ FlexLayout: Properties: alignItems: |- ``` - alignItems?: "center" | "start" | "end" | "stretch" + alignItems?: "end" | "start" | "center" | "stretch" ``` The `alignItems` property specifies the default alignment for items inside the container. @@ -7361,9 +7361,9 @@ FlexLayout: alignContent: |- ``` alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7382,7 +7382,7 @@ FlexLayout: * 'stretch': Content is stretched to fill the container. justifyItems: |- ``` - justifyItems?: "center" | "start" | "end" | "stretch" + justifyItems?: "end" | "start" | "center" | "stretch" ``` The `justifyItems` property specifies the default justification for items inside the container. @@ -7395,9 +7395,9 @@ FlexLayout: justifyContent: |- ``` justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7856,23 +7856,23 @@ GridLayout: ``` interface GridLayout { - alignItems?: "center" | "start" | "end" | "stretch"; + alignItems?: "end" | "start" | "center" | "stretch"; alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" | "space-evenly"; - justifyItems?: "center" + justifyItems?: "end" | "start" - | "end" + | "center" | "stretch"; justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7915,7 +7915,7 @@ GridLayout: Properties: alignItems: |- ``` - alignItems?: "center" | "start" | "end" | "stretch" + alignItems?: "end" | "start" | "center" | "stretch" ``` The `alignItems` property specifies the default alignment for items inside the container. @@ -7928,9 +7928,9 @@ GridLayout: alignContent: |- ``` alignContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -7949,7 +7949,7 @@ GridLayout: * 'stretch': Content is stretched to fill the container. justifyItems: |- ``` - justifyItems?: "center" | "start" | "end" | "stretch" + justifyItems?: "end" | "start" | "center" | "stretch" ``` The `justifyItems` property specifies the default justification for items inside the container. @@ -7962,9 +7962,9 @@ GridLayout: justifyContent: |- ``` justifyContent?: - | "center" - | "start" | "end" + | "start" + | "center" | "stretch" | "space-between" | "space-around" @@ -10592,7 +10592,7 @@ LayoutChildProperties: zIndex: number; horizontalSizing: "fill" | "auto" | "fix"; verticalSizing: "fill" | "auto" | "fix"; - alignSelf: "center" | "auto" | "start" | "end" | "stretch"; + alignSelf: "end" | "start" | "center" | "auto" | "stretch"; horizontalMargin: number; verticalMargin: number; topMargin: number; @@ -10645,7 +10645,7 @@ LayoutChildProperties: * 'fix': The height is fixed. alignSelf: |- ``` - alignSelf: "center" | "auto" | "start" | "end" | "stretch" + alignSelf: "end" | "start" | "center" | "auto" | "stretch" ``` Aligns the child element within its container. @@ -11244,7 +11244,7 @@ LibraryComponent: interface LibraryComponent { instance(): Shape; mainInstance(): Shape; - isVariant(): boolean; + isVariant(): this is LibraryVariantComponent; transformInVariant(): void; id: string; libraryId: string; @@ -11318,12 +11318,14 @@ LibraryComponent: Returns the reference to the main component shape. isVariant: |- ``` - isVariant(): boolean + isVariant(): this is LibraryVariantComponent ``` - Returns boolean + Checks whether this component is a variant component. + If true, the component can be used as a `LibraryVariantComponent`, + which provides additional attributes (e.g. `variants`) and methods. - true when this component is a VariantComponent + Returns this is LibraryVariantComponent transformInVariant: |- ``` transformInVariant(): void @@ -11474,7 +11476,7 @@ LibraryVariantComponent: interface LibraryVariantComponent { instance(): Shape; mainInstance(): Shape; - isVariant(): boolean; + isVariant(): this is LibraryVariantComponent; transformInVariant(): void; variants: Variants | null; variantProps: { [property: string]: string }; @@ -11499,7 +11501,7 @@ LibraryVariantComponent: * LibraryComponent + LibraryVariantComponent - Referenced by: ContextTypesUtils + Referenced by: ContextTypesUtils, LibraryComponent, LibraryVariantComponent members: Properties: variants: |- @@ -11571,12 +11573,14 @@ LibraryVariantComponent: Returns the reference to the main component shape. isVariant: |- ``` - isVariant(): boolean + isVariant(): this is LibraryVariantComponent ``` - Returns boolean + Checks whether this component is a variant component. + If true, the component can be used as a `LibraryVariantComponent`, + which provides additional attributes (e.g. `variants`) and methods. - true when this component is a VariantComponent + Returns this is LibraryVariantComponent transformInVariant: |- ``` transformInVariant(): void diff --git a/mcp/pnpm-lock.yaml b/mcp/pnpm-lock.yaml index 16a7487e30..5eac2b0d0c 100644 --- a/mcp/pnpm-lock.yaml +++ b/mcp/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: devDependencies: concurrently: - specifier: ^9.2.1 - version: 9.2.1 + specifier: ^10.0.3 + version: 10.0.3 prettier: - specifier: ^3.0.0 - version: 3.8.1 + specifier: ^3.8.4 + version: 3.8.4 packages/common: devDependencies: @@ -68,7 +68,7 @@ importers: version: 0.3.0 penpot-mcp: specifier: file:.. - version: packages@file:packages + version: file:packages pino: specifier: ^9.10.0 version: 9.14.0 @@ -1031,13 +1031,13 @@ packages: engines: {'0': node >= 0.8.0} hasBin: true - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1068,10 +1068,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -1082,21 +1078,14 @@ packages: class-validator@0.14.3: resolution: {integrity: sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} cluster-key-slot@1.1.1: resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} engines: {node: '>=0.10.0'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -1104,9 +1093,9 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} - engines: {node: '>=18'} + concurrently@10.0.3: + resolution: {integrity: sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==} + engines: {node: '>=22'} hasBin: true content-disposition@1.0.1: @@ -1176,8 +1165,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} @@ -1291,6 +1280,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1303,10 +1296,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1341,10 +1330,6 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -1435,9 +1420,6 @@ packages: resolution: {integrity: sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==} engines: {node: '>=12'} - packages@file:packages: - resolution: {directory: packages, type: directory} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1449,6 +1431,9 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + penpot-mcp@file:packages: + resolution: {directory: packages, type: directory} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1485,8 +1470,8 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} engines: {node: '>=14'} hasBin: true @@ -1530,10 +1515,6 @@ packages: reflect-metadata@0.1.14: resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1588,8 +1569,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} side-channel-list@1.0.0: @@ -1626,25 +1607,21 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} strip-json-comments@5.0.3: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} @@ -1761,9 +1738,9 @@ packages: engines: {node: '>= 8'} hasBin: true - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -1784,13 +1761,13 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} @@ -2367,11 +2344,9 @@ snapshots: ansi-html@0.0.9: {} - ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 + ansi-styles@6.2.3: {} arg@4.1.3: {} @@ -2407,11 +2382,6 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@5.6.2: {} class-transformer@0.5.1: {} @@ -2422,32 +2392,26 @@ snapshots: libphonenumber-js: 1.12.35 validator: 13.15.26 - cliui@8.0.1: + cliui@9.0.1: dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 cluster-key-slot@1.1.1: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - colorette@2.0.20: {} commander@12.1.0: {} - concurrently@9.2.1: + concurrently@10.0.3: dependencies: - chalk: 4.1.2 + chalk: 5.6.2 rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 + shell-quote: 1.8.4 + supports-color: 10.2.2 tree-kill: 1.2.2 - yargs: 17.7.2 + yargs: 18.0.0 content-disposition@1.0.1: {} @@ -2496,7 +2460,7 @@ snapshots: ee-first@1.1.1: {} - emoji-regex@8.0.0: {} + emoji-regex@10.6.0: {} encodeurl@2.0.0: {} @@ -2684,6 +2648,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2704,8 +2670,6 @@ snapshots: gopd@1.2.0: {} - has-flag@4.0.0: {} - has-symbols@1.1.0: {} hasown@2.0.2: @@ -2744,8 +2708,6 @@ snapshots: ipaddr.js@1.9.1: {} - is-fullwidth-code-point@3.0.0: {} - is-promise@4.0.0: {} isexe@2.0.0: {} @@ -2807,14 +2769,14 @@ snapshots: p-defer@4.0.1: {} - packages@file:packages: {} - parseurl@1.3.3: {} path-key@3.1.1: {} path-to-regexp@8.3.0: {} + penpot-mcp@file:packages: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} @@ -2872,7 +2834,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier@3.8.1: {} + prettier@3.8.4: {} process-warning@5.0.0: {} @@ -2911,8 +2873,6 @@ snapshots: reflect-metadata@0.1.14: {} - require-directory@2.1.1: {} - require-from-string@2.0.2: {} rollup@4.57.0: @@ -3032,7 +2992,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.3: {} + shell-quote@1.8.4: {} side-channel-list@1.0.0: dependencies: @@ -3074,25 +3034,19 @@ snapshots: statuses@2.0.2: {} - string-width@4.2.3: + string-width@7.2.0: dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 - strip-ansi@6.0.1: + strip-ansi@7.2.0: dependencies: - ansi-regex: 5.0.1 + ansi-regex: 6.2.2 strip-json-comments@5.0.3: {} - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 + supports-color@10.2.2: {} thread-stream@3.1.0: dependencies: @@ -3187,11 +3141,11 @@ snapshots: dependencies: isexe: 2.0.0 - wrap-ansi@7.0.0: + wrap-ansi@9.0.2: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -3199,17 +3153,16 @@ snapshots: y18n@5.0.8: {} - yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} - yargs@17.7.2: + yargs@18.0.0: dependencies: - cliui: 8.0.1 + cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 + string-width: 7.2.0 y18n: 5.0.8 - yargs-parser: 21.1.1 + yargs-parser: 22.0.0 yn@3.1.1: {} diff --git a/mcp/pnpm-workspace.yaml b/mcp/pnpm-workspace.yaml index 9bc2f715c9..7872ab47f7 100644 --- a/mcp/pnpm-workspace.yaml +++ b/mcp/pnpm-workspace.yaml @@ -1,6 +1,10 @@ +allowBuilds: + esbuild: true + sharp: false + linkWorkspacePackages: true packages: - - "./packages/common" - - "./packages/server" - - "./packages/plugin" + - "./packages/common" + - "./packages/server" + - "./packages/plugin" diff --git a/package.json b/package.json index db06aecab4..a1171fe177 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" @@ -20,6 +20,6 @@ "esbuild": "^0.28.0", "mdts": "^0.20.3", "nrepl-client": "^0.3.0", - "opencode-ai": "^1.16.2" + "opencode-ai": "^1.17.0" } } diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index 614a77407b..c9c48444c6 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -3,6 +3,7 @@ - **plugins-runtime**: Added `version` field that returns the current version - **plugins-runtime**: Added optional parameter `throwOnError` to `penpot.ui.sendMessage` (default false, backwards-compatible) - **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering` +- **plugin-types**: `penpot.openPage()` now returns `Promise<void>` and should be awaited before performing operations on the new page - **plugin-types**: Fix penpot.openPage() to navigate in same tab by default - **plugin-types:** Change `LibraryComponent.isVariant()` return type to type guard `this is LibraryVariantComponent` - **plugin-types**: Added `createVariantFromComponents` diff --git a/plugins/apps/colors-to-tokens-plugin/src/app/app.component.ts b/plugins/apps/colors-to-tokens-plugin/src/app/app.component.ts index 7403a5aceb..1e1e204541 100644 --- a/plugins/apps/colors-to-tokens-plugin/src/app/app.component.ts +++ b/plugins/apps/colors-to-tokens-plugin/src/app/app.component.ts @@ -1,6 +1,5 @@ -import { Component, effect, inject, linkedSignal } from '@angular/core'; +import { Component, effect, linkedSignal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { ActivatedRoute } from '@angular/router'; import type { PluginMessageEvent, PluginUIEvent, @@ -8,7 +7,7 @@ import type { SetColorsPluginEvent, TokenFileExtraData, } from '../model'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of } from 'rxjs'; import { transformToToken } from './utils/transform-to-token'; import { SvgComponent } from './components/svg.component'; @@ -70,14 +69,11 @@ import { SvgComponent } from './components/svg.component'; }, }) export class AppComponent { - route = inject(ActivatedRoute); messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message'); - initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); theme = toSignal( merge( diff --git a/plugins/apps/contrast-plugin/src/app/app.component.ts b/plugins/apps/contrast-plugin/src/app/app.component.ts index b482460f78..6c4e9263bf 100644 --- a/plugins/apps/contrast-plugin/src/app/app.component.ts +++ b/plugins/apps/contrast-plugin/src/app/app.component.ts @@ -1,17 +1,11 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - inject, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { ActivatedRoute } from '@angular/router'; import type { PluginMessageEvent, PluginUIEvent, ThemePluginEvent, } from '../model'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of } from 'rxjs'; import { CommonModule } from '@angular/common'; import { Shape } from '@penpot/plugin-types'; @@ -118,14 +112,11 @@ import { Shape } from '@penpot/plugin-types'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { - #route = inject(ActivatedRoute); #messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message'); - #initialTheme$ = this.#route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + #initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); selection = toSignal( this.#messages$.pipe( diff --git a/plugins/apps/icons-plugin/src/app/app.component.ts b/plugins/apps/icons-plugin/src/app/app.component.ts index 4c3ebc6ed3..ee24b3e356 100644 --- a/plugins/apps/icons-plugin/src/app/app.component.ts +++ b/plugins/apps/icons-plugin/src/app/app.component.ts @@ -1,10 +1,10 @@ -import { Component, inject, signal } from '@angular/core'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { Component, signal } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { FeatherIconNames, icons } from 'feather-icons'; import { IconButtonComponent } from './components/icon-button/icon-button.component'; import { IconSearchComponent } from './components/icon-search/icon-search.component'; import { toSignal } from '@angular/core/rxjs-interop'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of } from 'rxjs'; import { PluginMessageEvent } from '../model'; @Component({ @@ -36,7 +36,6 @@ import { PluginMessageEvent } from '../model'; }, }) export class AppComponent { - public route = inject(ActivatedRoute); public icons = signal(icons); public iconKeys = signal(Object.keys(icons) as FeatherIconNames[]); public messages$ = fromEvent<MessageEvent<PluginMessageEvent>>( @@ -44,11 +43,9 @@ export class AppComponent { 'message', ); - public initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + public initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); public theme = toSignal( merge( diff --git a/plugins/apps/lorem-ipsum-plugin/src/app/app.component.ts b/plugins/apps/lorem-ipsum-plugin/src/app/app.component.ts index 4c91dc0ba9..1016cc4a4e 100644 --- a/plugins/apps/lorem-ipsum-plugin/src/app/app.component.ts +++ b/plugins/apps/lorem-ipsum-plugin/src/app/app.component.ts @@ -1,13 +1,12 @@ -import { Component, inject } from '@angular/core'; +import { Component } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; import type { GenerationTypes, PluginMessageEvent, PluginUIEvent, } from '../model'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of } from 'rxjs'; @Component({ imports: [ReactiveFormsModule], @@ -67,14 +66,11 @@ import { filter, fromEvent, map, merge, take } from 'rxjs'; }, }) export class AppComponent { - route = inject(ActivatedRoute); messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message'); - initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); theme = toSignal( merge( diff --git a/plugins/apps/poc-tokens-plugin/src/app/app.component.ts b/plugins/apps/poc-tokens-plugin/src/app/app.component.ts index 8b60e40d8d..089649f6ac 100644 --- a/plugins/apps/poc-tokens-plugin/src/app/app.component.ts +++ b/plugins/apps/poc-tokens-plugin/src/app/app.component.ts @@ -1,7 +1,6 @@ -import { Component, inject } from '@angular/core'; +import { Component } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { ActivatedRoute } from '@angular/router'; -import { fromEvent, map, filter, take, merge } from 'rxjs'; +import { fromEvent, map, filter, merge, of } from 'rxjs'; import { PluginMessageEvent, PluginUIEvent } from '../model'; type TokenTheme = { @@ -39,18 +38,14 @@ type TokensGroup = [string, Token[]]; }, }) export class AppComponent { - public route = inject(ActivatedRoute); - public messages$ = fromEvent<MessageEvent<PluginMessageEvent>>( window, 'message', ); - public initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + public initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); public theme = toSignal( merge( diff --git a/plugins/apps/rename-layers-plugin/src/app/app.component.ts b/plugins/apps/rename-layers-plugin/src/app/app.component.ts index bc6fc72c59..70055739cc 100644 --- a/plugins/apps/rename-layers-plugin/src/app/app.component.ts +++ b/plugins/apps/rename-layers-plugin/src/app/app.component.ts @@ -1,5 +1,5 @@ -import { Component, ElementRef, ViewChild, inject } from '@angular/core'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { toSignal } from '@angular/core/rxjs-interop'; import { CommonModule } from '@angular/common'; import type { @@ -7,7 +7,7 @@ import type { ReplaceText, ThemePluginEvent, } from '../app/model'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of } from 'rxjs'; import { FormsModule } from '@angular/forms'; import { Shape } from '@penpot/plugin-types'; @@ -24,7 +24,6 @@ export class AppComponent { @ViewChild('searchElement') public searchElement!: ElementRef; @ViewChild('addElement') public addElement!: ElementRef; - route = inject(ActivatedRoute); messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message'); public textToReplace: ReplaceText = { search: '', @@ -38,11 +37,9 @@ export class AppComponent { this.sendMessage({ type: 'ready' }); } - initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); theme = toSignal( merge( diff --git a/plugins/apps/table-plugin/src/app/app.component.ts b/plugins/apps/table-plugin/src/app/app.component.ts index cb13e22061..4cce7de045 100644 --- a/plugins/apps/table-plugin/src/app/app.component.ts +++ b/plugins/apps/table-plugin/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component, inject } from '@angular/core'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { RouterModule } from '@angular/router'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import type { @@ -8,7 +8,7 @@ import type { TableConfigEvent, TableOptions, } from '../app/model'; -import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { filter, fromEvent, map, merge, of, take } from 'rxjs'; import { FormBuilder, ReactiveFormsModule, FormGroup } from '@angular/forms'; @Component({ @@ -37,14 +37,11 @@ export class AppComponent { alternateRows: [false], }); - route = inject(ActivatedRoute); messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message'); - initialTheme$ = this.route.queryParamMap.pipe( - map((params) => params.get('theme')), - filter((theme) => !!theme), - take(1), - ); + initialTheme$ = of( + new URLSearchParams(window.location.search).get('theme'), + ).pipe(filter((theme) => !!theme)); theme = toSignal( merge( diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index bad2ce5e9f..18d8eafcfa 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -13,7 +13,7 @@ export interface Penpot extends Omit< * * @param name title of the plugin, it'll be displayed on the top of the modal * @param url of the plugin - * @param options height and width of the modal. + * @param options width and height of the modal and, optionally, a `hidden` flag to open the plugin without showing the modal. * * @example * ```js @@ -23,19 +23,25 @@ export interface Penpot extends Omit< open: ( name: string, url: string, - options?: { width: number; height: number; hidden: boolean }, + options?: { width: number; height: number; hidden?: boolean }, ) => void; + /** + * The current size of the modal, or `null` if the plugin UI is not open. + * @example + * ```js + * const size = penpot.ui.size; + * console.log(size); + * ``` + */ size: { /** - * Returns the size of the modal. - * @example - * ```js - * const size = penpot.ui.size; - * console.log(size); - * ``` + * The width of the modal. */ width: number; + /** + * The height of the modal. + */ height: number; } | null; @@ -58,19 +64,19 @@ export interface Penpot extends Omit< * * @example * ```js - * this.sendMessage({ type: 'example-type', content: 'data we want to share' }); + * penpot.ui.sendMessage({ type: 'example-type', content: 'data we want to share' }); * ``` */ sendMessage: (message: unknown, throwOnError?: boolean) => void; /** - * This is usually used in the `plugin.ts` file in order to handle the data sent by our plugin + * This is usually used in the `plugin.ts` file in order to handle the messages sent from the plugin UI. * - @param callback - A function that will be called whenever a message is received. + * @param callback - A function that will be called whenever a message is received. * The function receives a single argument, `message`, which is of type `T`. * * @example * ```js - * penpot.ui.onMessage((message) => {if(message.type === 'example-type' { ...do something })}); + * penpot.ui.onMessage((message) => { if (message.type === 'example-type') { ...do something } }); * ``` */ onMessage: <T>(callback: (message: T) => void) => void; @@ -102,7 +108,9 @@ export interface Penpot extends Omit< * ``` * - selectionchange: event emitted when the current selection changes. The callback will receive the list of ids for the new selection * - themechange: event emitted when the user changes its theme. The callback will receive the new theme (currently: either `dark` or `light`) - * - documentsaved: event emitted after the document is saved in the backend. + * - filechange: event emitted when a different file is opened. The callback will receive the new file. + * - contentsave: event emitted after the file content is saved in the backend. The callback receives no arguments. + * - finish: event emitted when the current file is closed. The callback will receive the id of the closed file. * * @param type The event type to listen for. * @param callback The callback function to execute when the event is triggered. @@ -111,7 +119,7 @@ export interface Penpot extends Omit< * * @example * ```js - * penpot.on('pagechange', () => {...do something}). + * penpot.on('pagechange', () => {...do something}); * ``` */ on<T extends keyof EventsMap>( @@ -218,7 +226,7 @@ export interface Board extends ShapeBase { clipContent: boolean; /** - * WHen true the board will be displayed in the view mode + * When true the board will be displayed in the view mode */ showInViewMode: boolean; @@ -245,7 +253,7 @@ export interface Board extends ShapeBase { /** * The horizontal sizing behavior of the board. * It can be one of the following values: - * - 'fix': The containers has its own intrinsic fixed size. + * - 'fix': The container has its own intrinsic fixed size. * - 'auto': The container fits the content. */ horizontalSizing?: 'auto' | 'fix'; @@ -253,7 +261,7 @@ export interface Board extends ShapeBase { /** * The vertical sizing behavior of the board. * It can be one of the following values: - * - 'fix': The containers has its own intrinsic fixed size. + * - 'fix': The container has its own intrinsic fixed size. * - 'auto': The container fits the content. */ verticalSizing?: 'auto' | 'fix'; @@ -333,16 +341,19 @@ export interface Board extends ShapeBase { * grid.columnGap = 10; * grid.verticalPadding = 5; * grid.horizontalPadding = 5; + * ``` */ addGridLayout(): GridLayout; /** - * Creates a new ruler guide. + * Creates a new ruler guide attached to the board. + * @param orientation `horizontal` or `vertical` + * @param value the position of the guide relative to the board */ addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide; /** - * Removes the `guide` from the current page. + * Removes the `guide` from the board. */ removeRulerGuide(guide: RulerGuide): void; @@ -369,7 +380,7 @@ export interface VariantContainer extends Board { */ export interface Boolean extends ShapeBase { /** - * The type of the shape, which is always 'bool' for boolean operation shapes. + * The type of the shape, which is always 'boolean' for boolean operation shapes. */ readonly type: 'boolean'; @@ -745,7 +756,7 @@ export interface CommonLayout { /** * The `horizontalSizing` property specifies the horizontal sizing behavior of the container. * It can be one of the following values: - * - 'fix': The containers has its own intrinsic fixed size. + * - 'fix': The container has its own intrinsic fixed size. * - 'fill': The container fills the available space. Only can be set if it's inside another layout. * - 'auto': The container fits the content. */ @@ -753,7 +764,7 @@ export interface CommonLayout { /** * The `verticalSizing` property specifies the vertical sizing behavior of the container. * It can be one of the following values: - * - 'fix': The containers has its own intrinsic fixed size. + * - 'fix': The container has its own intrinsic fixed size. * - 'fill': The container fills the available space. Only can be set if it's inside another layout. * - 'auto': The container fits the content. */ @@ -979,9 +990,9 @@ export interface Context { * @example * ```js * const penpotShapesArray = penpot.selection; - * // We need to make sure that something is selected, and if the selected shape is a group, - * if (selected.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { - * penpot.group(penpotShapesArray[0]); + * // We need to make sure that something is selected, and that the selected shape is a group + * if (penpotShapesArray.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { + * penpot.ungroup(penpotShapesArray[0]); * } * ``` */ @@ -1180,8 +1191,8 @@ export interface Context { /** * Generates markup for the given shapes. Requires `content:read` permission - * @param shapes - * @param options + * @param shapes the shapes to generate the markup for + * @param options `type` of the markup to generate. Defaults to `'html'`. * * @example * ```js @@ -1193,8 +1204,9 @@ export interface Context { /** * Generates styles for the given shapes. Requires `content:read` permission - * @param shapes - * @param options + * @param shapes the shapes to generate the styles for + * @param options `type` of the styles to generate (defaults to `'css'`), `withPrelude` to include the style prelude + * (defaults to `false`) and `includeChildren` to also generate the styles for the shape children (defaults to `true`). * * @example * ```js @@ -1267,10 +1279,10 @@ export interface Context { * * @example * ```js - * context.openPage(page); + * await context.openPage(page); * ``` */ - openPage(page: Page | string, newWindow?: boolean): void; + openPage(page: Page | string, newWindow?: boolean): Promise<void>; /** * Aligning will move all the selected layers to a position relative to one @@ -1489,6 +1501,9 @@ export interface Dissolve { * This interface extends `ShapeBase` and includes properties specific to ellipses. */ export interface Ellipse extends ShapeBase { + /** + * The type of the shape, which is always 'ellipse' for ellipse shapes. + */ type: 'ellipse'; /** @@ -1514,7 +1529,8 @@ export interface EventsMap { */ pagechange: Page; /** - * The `filechange` event is triggered when there are changes in the current file. + * The `filechange` event is triggered when a different file is opened. + * The callback will receive the new file. */ filechange: File; /** @@ -1527,7 +1543,8 @@ export interface EventsMap { */ themechange: Theme; /** - * The `finish` event is triggered when some operation is finished. + * The `finish` event is triggered when the current file is closed. + * The callback will receive the id of the closed file. */ finish: string; @@ -1538,7 +1555,7 @@ export interface EventsMap { shapechange: Shape; /** - * The `contentsave` event will trigger when the content file changes. + * The `contentsave` event will trigger after the file content is saved in the backend. */ contentsave: void; } @@ -1591,7 +1608,7 @@ export interface File extends PluginData { */ pages: Page[]; - /* + /** * Export the current file to an archive. * @param `exportType` indicates the type of file to generate. * - `'penpot'` will create a *.penpot file with a binary representation of the file @@ -1601,7 +1618,6 @@ export interface File extends PluginData { * - `'all'` will include the libraries as external files that will be exported in a single bundle * - `'merge'` will add all the assets into the main file and only one file will be imported * - `'detach'` will unlink all the external assets and no libraries will be imported - * @param `progressCallback` for `zip` export can be pass this callback so a progress report is sent. * * @example * ```js @@ -1652,7 +1668,7 @@ export interface FileVersion { */ readonly isAutosave: boolean; - /* + /** * Restores the current version and replaces the content of the active file * for the contents of this version. * Requires the `content:write` permission. @@ -1706,13 +1722,13 @@ export interface Fill { } /** - * This subcontext allows the API o change certain defaults + * This subcontext allows the API to change certain defaults */ export interface Flags { /** - * If `true` the .children property will be always sorted in the z-index ordering. - * Also, appendChild method will be append the children in the top-most position. - * The insertchild method is changed acordingly to respect this ordering. + * If `true` the .children property will always be sorted in the z-index ordering. + * Also, the appendChild method will append the children in the top-most position. + * The insertChild method is changed accordingly to respect this ordering. * Defaults to false */ naturalChildOrdering: boolean; @@ -2172,7 +2188,7 @@ export interface Group extends ShapeBase { export type Guide = GuideColumn | GuideRow | GuideSquare; /** - * Represents a goard guide for columns in Penpot. + * Represents a board guide for columns in Penpot. * This interface includes properties for defining the type, visibility, and parameters of column guides within a board. */ export interface GuideColumn { @@ -2307,6 +2323,9 @@ export interface HistoryContext { * This interface extends `ShapeBase` and includes properties specific to image shapes. */ export interface Image extends ShapeBase { + /** + * The type of the shape, which is always 'image' for image shapes. + */ type: 'image'; /** @@ -2347,7 +2366,7 @@ export type ImageData = { keepAspectRatio?: boolean; /** - * Returns the imaged data as a byte array. + * Returns the image data as a byte array. */ data(): Promise<Uint8Array>; }; @@ -2680,7 +2699,7 @@ export interface LibraryComponent extends LibraryElement { isVariant(): this is LibraryVariantComponent; /** - * Creates a new Variant from this standard Component. It creates a VariantContainer, transform this Component into a VariantComponent, duplicates it, and creates a + * Creates a new Variant from this standard Component. It creates a VariantContainer, transforms this Component into a VariantComponent, duplicates it, and creates a * set of properties based on the component name and path. * Similar to doing it with the contextual menu or the shortcut on the Penpot interface */ @@ -2698,12 +2717,12 @@ export interface LibraryVariantComponent extends LibraryComponent { readonly variants: Variants | null; /** - * A list of the variants props of this VariantComponent. Each property have a key and a value + * A list of the variant props of this VariantComponent. Each property has a key and a value */ readonly variantProps: { [property: string]: string }; /** - * If this VariantComponent has an invalid name, that does't follow the structure [property]=[value], [property]=[value] + * If this VariantComponent has an invalid name, that doesn't follow the structure [property]=[value], [property]=[value] * this field stores that invalid name */ variantError: string; @@ -2715,8 +2734,9 @@ export interface LibraryVariantComponent extends LibraryComponent { /** * Sets the value of the variant property on the indicated position + * @param pos The position of the property to update + * @param value The new value of the property */ - setVariantProperty(pos: number, value: string): void; } @@ -2915,7 +2935,7 @@ export interface LibraryTypography extends LibraryElement { * Proxy for the local storage. Only elements owned by the plugin * can be stored and accessed. * Warning: other plugins won't be able to access this information but - * the user could potentialy access the data through the browser information. + * the user could potentially access the data through the browser information. */ export interface LocalStorage { /** @@ -2926,7 +2946,7 @@ export interface LocalStorage { /** * Set the data given the key. If the value already existed it - * will be overriden. The value will be stored in a string representation. + * will be overridden. The value will be stored in a string representation. * Requires the `allow:localstorage` permission. */ setItem(key: string, value: unknown): void; @@ -3056,7 +3076,7 @@ export interface Page extends PluginData { name: string; /** - * The ruler guides attached to the board + * The ruler guides attached to the page */ readonly rulerGuides: RulerGuide[]; @@ -3079,7 +3099,7 @@ export interface Page extends PluginData { /** * Finds all shapes on the page. - * Optionaly it gets a criteria object to search for specific criteria + * Optionally it gets a criteria object to search for specific criteria * @param criteria * @example * ```js @@ -3126,6 +3146,9 @@ export interface Page extends PluginData { /** * Creates a new ruler guide. + * @param orientation `horizontal` or `vertical` + * @param value the position of the guide in absolute coordinates + * @param board if provided, the guide will be attached to the board */ addRulerGuide( orientation: RulerGuideOrientation, @@ -3139,8 +3162,7 @@ export interface Page extends PluginData { removeRulerGuide(guide: RulerGuide): void; /** - * Creates a new comment thread in the `position`. Optionaly adds - * it into the `board`. + * Creates a new comment thread in the `position`. * Returns the thread created. * Requires the `comment:write` permission. */ @@ -3183,18 +3205,18 @@ export interface Path extends ShapeBase { toD(): string; /** - * The content of the boolean shape, defined as the path string. + * The content of the path shape, defined as the path string. * @deprecated Use either `d` or `commands`. */ content: string; /** - * The content of the boolean shape, defined as the path string. + * The content of the path shape, defined as the path string. */ d: string; /** - * The content of the boolean shape, defined as an array of path commands. + * The content of the path shape, defined as an array of path commands. */ commands: Array<PathCommand>; @@ -3434,7 +3456,7 @@ export interface Push { readonly duration: number; /** - * Function that the dissolve effect will follow for the interpolation. + * Function that the push effect will follow for the interpolation. * Defaults to `linear` */ readonly easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; @@ -3467,8 +3489,8 @@ export interface RulerGuide { readonly orientation: RulerGuideOrientation; /** - * `position` is the position in the axis in absolute positioning. If this is a board - * guide will return the positioning relative to the board. + * `position` is the position in the axis in absolute coordinates. If this is a board + * guide it will return the position relative to the board. */ position: number; @@ -3479,7 +3501,7 @@ export interface RulerGuide { } /** - * + * The possible orientations for a ruler guide: 'horizontal' or 'vertical'. */ export type RulerGuideOrientation = 'horizontal' | 'vertical'; @@ -3595,12 +3617,12 @@ export interface ShapeBase extends PluginData { readonly height: number; /** - * @return Returns the bounding box surrounding the current shape + * The bounding box surrounding the current shape */ readonly bounds: Bounds; /** - * @return Returns the geometric center of the shape + * The geometric center of the shape */ readonly center: Point; @@ -3731,7 +3753,7 @@ export interface ShapeBase extends PluginData { flipY: boolean; /** - * @return Returns the rotation in degrees of the shape with respect to it's center. + * The rotation in degrees of the shape with respect to its center. */ rotation: number; @@ -3832,13 +3854,16 @@ export interface ShapeBase extends PluginData { detach(): void; /** - * TODO + * Swaps the component instance for another component, keeping the overrides + * when possible. Similar to the "swap component" action on the Penpot interface. + * The current shape must be a component copy instance. + * @param component The new component to replace the current one */ swapComponent(component: LibraryComponent): void; /** * Switch a VariantComponent copy to the nearest one that has the specified property value - * @param pos The position of the poroperty to update + * @param pos The position of the property to update * @param value The new value of the property */ switchVariant(pos: number, value: string): void; @@ -3873,7 +3898,7 @@ export interface ShapeBase extends PluginData { /** * Rotates the shape in relation with the given center. * @param angle Angle in degrees to rotate. - * @param center Center of the transform rotation. If not send will use the geometri center of the shapes. + * @param center Center of the transform rotation. If not sent it will use the geometric center of the shape. * * @example * ```js @@ -3952,7 +3977,7 @@ export interface ShapeBase extends PluginData { * and the value set to the attributes will depend on which sets are active * (and will change if different sets or themes are activated later). */ - applyToken(token: Token, properties: TokenProperty[] | undefined): void; + applyToken(token: Token, properties?: TokenProperty[]): void; /** * Creates a clone of the shape. @@ -3996,7 +4021,7 @@ export interface Slide { readonly offsetEffect?: boolean; /** - * Function that the dissolve effect will follow for the interpolation. + * Function that the slide effect will follow for the interpolation. * Defaults to `linear`. */ readonly easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; @@ -4068,6 +4093,9 @@ export type StrokeCap = * This interface extends `ShapeBase` and includes properties specific to raw SVG shapes. */ export interface SvgRaw extends ShapeBase { + /** + * The type of the shape, which is always 'svg-raw' for raw SVG shapes. + */ type: 'svg-raw'; } @@ -4154,7 +4182,7 @@ export interface Text extends ShapeBase { align: 'left' | 'center' | 'right' | 'justify' | 'mixed' | null; /** - * The vertical alignment of the text shape. It can be a specific alignment or 'mixed' if multiple alignments are used. + * The vertical alignment of the text shape. */ verticalAlign: 'top' | 'center' | 'bottom' | null; @@ -4402,14 +4430,14 @@ export interface TokenBase { * and the value set to the attributes will depend on which sets are active * (and will change if different sets or themes are activated later). */ - applyToShapes(shapes: Shape[], properties: TokenProperty[] | undefined): void; + applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; /** * Applies this token to the currently selected shapes. * * Parameters and warnings are the same as above. */ - applyToSelected(properties: TokenProperty[] | undefined): void; + applyToSelected(properties?: TokenProperty[]): void; } /** @@ -4437,7 +4465,7 @@ export interface TokenBorderRadius extends TokenBase { readonly resolvedValue: number | undefined; } -/* +/** * The value of a TokenShadow in its composite form. */ export interface TokenShadowValue { @@ -4472,7 +4500,7 @@ export interface TokenShadowValue { blur: number; } -/* +/** * The value of a TokenShadow in its composite of strings form. */ export interface TokenShadowValueString { @@ -4557,8 +4585,10 @@ export interface TokenColor extends TokenBase { value: string; /** - * The value as defined in the token itself. - * It's a rgb color or a reference. + * The value calculated by finding all tokens with the same name in active sets + * and resolving the references. + * + * It's a rgb color, or undefined if no value has been found in active sets. */ readonly resolvedValue: string | undefined; } @@ -4896,7 +4926,7 @@ export interface TokenTextDecoration extends TokenBase { readonly resolvedValue: string | undefined; } -/* +/** * The value of a TokenTypography in its composite form. */ export interface TokenTypographyValue { @@ -4936,7 +4966,7 @@ export interface TokenTypographyValue { textDecoration: string; } -/* +/** * The value of a TokenTypography in its composite of strings form. */ export interface TokenTypographyValueString { @@ -4962,9 +4992,9 @@ export interface TokenTypographyValueString { fontWeight: string; /** - * The line height, as a number. Note that there not exists an individual - * token type line height, only part of a Typography token. If you need to - * put here a reference, use a NumberToken. + * The line height, as a number. Note that there is no individual line-height + * token type; it only exists as part of a Typography token. If you need to + * put a reference here, use a Number token. */ lineHeight: string; @@ -5055,8 +5085,8 @@ export interface TokenCatalog { readonly themes: TokenTheme[]; /** - * The list of sets in this catalog, in the order defined - * by the user. The order is important because then same token name + * The list of sets in this catalog, in the order defined + * by the user. The order is important because when the same token name * exists in several active sets, the latter has precedence. */ readonly sets: TokenSet[]; @@ -5140,7 +5170,7 @@ export interface TokenSet { /** * Creates a new Token and adds it to the set. - * @param type Thetype of token. + * @param type The type of the token. * @param name The name of the token (required). It may contain * a group path, separated by `.`. * @param value The value of the token (required), in the string form. @@ -5175,9 +5205,9 @@ export interface TokenSet { * sets that are _not_ in this theme, because they may have been * activated by other themes. * - * Themes may be gruped. At any time only one of the themes in a group + * Themes may be grouped. At any time only one of the themes in a group * may be active. But there may be active themes in other groups. This - * allows to define multiple "axis" for theming (e.g. color scheme, + * allows defining multiple "axis" for theming (e.g. color scheme, * density or brand). * * When a TokenSet is activated or deactivated directly, all themes @@ -5192,13 +5222,13 @@ export interface TokenTheme { readonly id: string; /** - * Optional identifier that may exists if the theme was imported from an + * Optional identifier that may exist if the theme was imported from an * external tool that uses ids in the json file. */ readonly externalId: string | undefined; /** - * The group name of the theme. Can be empt string. + * The group name of the theme. Can be an empty string. */ group: string; @@ -5466,7 +5496,10 @@ export interface User { } /** - * TODO + * Represents a Variant in Penpot: the grouping of all the VariantComponents that + * belong to the same VariantContainer, together with their shared properties. + * This interface provides attributes and actions that affect the Variant as a + * whole (not a single VariantComponent). */ export interface Variants { /** @@ -5486,7 +5519,7 @@ export interface Variants { properties: string[]; /** - * A list of all the values of a property along all the variantComponents of this Variant + * A list of all the values of a property across all the VariantComponents of this Variant * @param property The name of the property */ currentValues(property: string): string[]; diff --git a/plugins/libs/plugins-runtime/src/lib/api/index.ts b/plugins/libs/plugins-runtime/src/lib/api/index.ts index 885967a2f1..6faa7b1925 100644 --- a/plugins/libs/plugins-runtime/src/lib/api/index.ts +++ b/plugins/libs/plugins-runtime/src/lib/api/index.ts @@ -348,9 +348,9 @@ export function createApi( return plugin.context.createPage(); }, - openPage(page: Page | string, newWindow?: boolean): void { + openPage(page: Page | string, newWindow?: boolean): Promise<void> { checkPermission('content:read'); - plugin.context.openPage(page, newWindow ?? false); + return plugin.context.openPage(page, newWindow ?? false); }, alignHorizontal( diff --git a/plugins/package.json b/plugins/package.json index f0249f3c56..7a97ee50da 100644 --- a/plugins/package.json +++ b/plugins/package.json @@ -3,7 +3,7 @@ "version": "0.6.0", "type": "module", "license": "MIT", - "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268", + "packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620", "scripts": { "start": "pnpm run start:app:runtime", "start:app:runtime": "concurrently --kill-others --names build,server \"pnpm --filter @penpot/plugins-runtime run build:watch\" \"pnpm --filter @penpot/plugins-runtime run preview\"", diff --git a/plugins/pnpm-workspace.yaml b/plugins/pnpm-workspace.yaml index 25cb95c24b..f69b09ccd7 100644 --- a/plugins/pnpm-workspace.yaml +++ b/plugins/pnpm-workspace.yaml @@ -1,5 +1,12 @@ packages: - 'apps/**' - 'libs/**' +allowBuilds: + '@parcel/watcher': false + core-js: true + esbuild: true + lmdb: false + msgpackr-extract: false + puppeteer: false linkWorkspacePackages: true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff4a459ce3..b42a208a2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^0.3.0 version: 0.3.0 opencode-ai: - specifier: ^1.16.2 - version: 1.16.2 + specifier: ^1.17.0 + version: 1.17.0 packages: @@ -545,72 +545,72 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} - opencode-ai@1.16.2: - resolution: {integrity: sha512-70w3KxB0tKEA0Fy66McSXY3v5qv3AOX76PXdc0WxQBzEzizCpJtNBp3frMd5VJ+ASwrSe4DxmY3Ve/OByzriMw==} + opencode-ai@1.17.0: + resolution: {integrity: sha512-YDRKBJ469vAK9Vtx5EMbRSo/BU+iL+Lit0sbGtorjC4bU6tb76pKTIhD11tnseRYx6MLv4XrypPDyZedEIk02A==} cpu: [arm64, x64] os: [darwin, linux, win32] hasBin: true - opencode-darwin-arm64@1.16.2: - resolution: {integrity: sha512-oJu1SSaXK1GZr14JWvLt9C+CH8+enonfuNerJG7jQcxkMxZCgS36Y4YJNMKYda7N+0u02j02b1y21/StQpo3xQ==} + opencode-darwin-arm64@1.17.0: + resolution: {integrity: sha512-heL151oyckrFxpu8Bdf9BTYdXb4OTCXwTZ8NI+O1iGakzeRLkNnoVER76uS6DO6Aoj/De8xUOub3o0yCn0Kuwg==} cpu: [arm64] os: [darwin] - opencode-darwin-x64-baseline@1.16.2: - resolution: {integrity: sha512-51vuOj6DpbkaR9UIYs0r/9QQrSuJuAabKU1JXIBINOXDbam4hquF9ppeW7Nbh4KTdrsxK7dt277tVn6zDoar6g==} + opencode-darwin-x64-baseline@1.17.0: + resolution: {integrity: sha512-rwWQDyioFd4p2WrVbenBPYy3u01R8FyMrbOjgj1003CXRuY5W3UmsgoIlFwJUA09EGHiu0rvU4dwA0WaLhsfsA==} cpu: [x64] os: [darwin] - opencode-darwin-x64@1.16.2: - resolution: {integrity: sha512-uqY+FPzJ/bT18eNuDYrR5TTA7VMMLbfX4lmFrJLCqUKnwBI/ePEdf0cbxQ4cF16figixb8NjR7VMncR9oFZ7FA==} + opencode-darwin-x64@1.17.0: + resolution: {integrity: sha512-8ebo4FZI1LhGXW7Yihm8J2J550ulLa50RkznRJrufod63ki6D6g/VrH5PkvAXNwpIQ7wZU1QnHUmgRb6OSF5ww==} cpu: [x64] os: [darwin] - opencode-linux-arm64-musl@1.16.2: - resolution: {integrity: sha512-0DDPAgfegptm3pzJGcoh/8Iv/Lq7Ew/GR0FHofpKEi71H5sNElzu2Qx2XOPfBxqLfEP3ReaIYKvFYroJXtHhdQ==} + opencode-linux-arm64-musl@1.17.0: + resolution: {integrity: sha512-MsgHfzE6+feVsrUMBvPoCcE5QF8EMcufkSMgbdJCnyRXS/v6cuoaRPNz5sIMknpfPnGaCUnHYLiyOtjeEe0NNA==} cpu: [arm64] os: [linux] libc: [musl] - opencode-linux-arm64@1.16.2: - resolution: {integrity: sha512-BwW0K+fAkf4iI+WbBwskct2LcDEZVxS5K+dNER6HcAqOkljVmTlqW/rnXw3/9ZFgKDMieZAi2J7wYhkMXV9z4g==} + opencode-linux-arm64@1.17.0: + resolution: {integrity: sha512-T21kUGraOA7EBLwOg+SELrc6vAK8l3Vj5JXrOtm9+OQEUIL8yU4yyJlgxLSTh2RnGBhsOGxPwLm2L1o0nmy6ow==} cpu: [arm64] os: [linux] - opencode-linux-x64-baseline-musl@1.16.2: - resolution: {integrity: sha512-LqQ8rzUc35i2Ey6JsTu/vwTqlHWMk6DcTvCdj9RPyHHz9bzTgfwpvKqXMLYQr74rhaSWkSaSaSi+iELakWlg7Q==} + opencode-linux-x64-baseline-musl@1.17.0: + resolution: {integrity: sha512-rW9DU2oiMWFDQtM1O8I96cjQu7bpKkv+Nmh0VyK8dUjPx2vsTl4eNBVQmLSCevkSCd81rP8uu8pmZ3cr2xxO+A==} cpu: [x64] os: [linux] libc: [musl] - opencode-linux-x64-baseline@1.16.2: - resolution: {integrity: sha512-6ethaqt1i/eSE7HaKxabo3K0rfE8JYYFQmBDvCXF0Du32irmpXX7CrA+3qQ1/6j/gg0epFhDq/b0HSFMAoCDWw==} + opencode-linux-x64-baseline@1.17.0: + resolution: {integrity: sha512-f1kDGqOGUQxrgWkNmZSVAVzD0xAxTfkXRIukHNw7r3IblgY0+PZB5XEq8KDMskzdJRwdYqv2cPyjv+D6pThkcQ==} cpu: [x64] os: [linux] - opencode-linux-x64-musl@1.16.2: - resolution: {integrity: sha512-oPHAOptdQ0d346AoiBZ5/TIJS3QqMV7uUFSIFcH7AHisx7EBNNfVmZ+Qcq7oJmNNXaHlV2Uf/vkL5H4aX3E9Ww==} + opencode-linux-x64-musl@1.17.0: + resolution: {integrity: sha512-upbH4j5PDwL+7mFRUYclK708kJ0zq1snnb+r2rtHxWSbNTfLd8ZJnUS+rXuPpymWHWdvwB8STl/66lP63dt/cQ==} cpu: [x64] os: [linux] libc: [musl] - opencode-linux-x64@1.16.2: - resolution: {integrity: sha512-O+EKhZ0xGrmxP0v1UuW62FbMborzrYnQ3rKy/ulYWfz9TGhUxu7gSWceBcASXx00T6HM94ob8atE8MnfEzZ0Qg==} + opencode-linux-x64@1.17.0: + resolution: {integrity: sha512-WIH0gewBkD3gt7K3R12/1KqV4nLIEVuzTJc1zBnPcGPOnR9HEid5qxGwgbM0DyfQC9pkhCszHT2Xx71TEScEGg==} cpu: [x64] os: [linux] - opencode-windows-arm64@1.16.2: - resolution: {integrity: sha512-yS/Tzmci60z5JVqyCTdDxvAwgM5gAhL4uAkNpZ8bqNy8gXK7QIrLIH4waiHyOFYwg0cWPS/nwUP4R+0o170wew==} + opencode-windows-arm64@1.17.0: + resolution: {integrity: sha512-iAvHYUS0GRJ+Utr1d4X4KwxmVi0nTM08ZhPkJtF1yzg38oTtHmxdDnkzTd2IIc/79XOfnRMzq47ejwnz9yWp7g==} cpu: [arm64] os: [win32] - opencode-windows-x64-baseline@1.16.2: - resolution: {integrity: sha512-GsqlwlU05rWqGU6Qxx9tDqR/EJtY08/PryjFXdLu5tLuNPNELhMLoVsgfXeZgo3lp0gA+RPqmW15Py48tbFEQA==} + opencode-windows-x64-baseline@1.17.0: + resolution: {integrity: sha512-A+jlLAvIiLUb8Z0mPy9orTPpJQplOGAiTbNM/If5cUvEvBnifwkXOg4YCYEcUwC0hEfbyV9uJRLTyvtTOR8z5g==} cpu: [x64] os: [win32] - opencode-windows-x64@1.16.2: - resolution: {integrity: sha512-QSfFS6dA62s6PWLHSQflZ7aFtSaRL/F4XIHDDdZi/m8Uu6/6dlqTVuDY2kInztEgz+c1r8WJwyhOcSBDjgoOUg==} + opencode-windows-x64@1.17.0: + resolution: {integrity: sha512-ayaNoZnxcF18LVO1zO7hscGpMZGrWaUDMpe2XHmLOz7x6yKECo3HuK0IwhXRBtsaq+KZ68pWYUjp54pgEWcduw==} cpu: [x64] os: [win32] @@ -1263,55 +1263,55 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - opencode-ai@1.16.2: + opencode-ai@1.17.0: optionalDependencies: - opencode-darwin-arm64: 1.16.2 - opencode-darwin-x64: 1.16.2 - opencode-darwin-x64-baseline: 1.16.2 - opencode-linux-arm64: 1.16.2 - opencode-linux-arm64-musl: 1.16.2 - opencode-linux-x64: 1.16.2 - opencode-linux-x64-baseline: 1.16.2 - opencode-linux-x64-baseline-musl: 1.16.2 - opencode-linux-x64-musl: 1.16.2 - opencode-windows-arm64: 1.16.2 - opencode-windows-x64: 1.16.2 - opencode-windows-x64-baseline: 1.16.2 + opencode-darwin-arm64: 1.17.0 + opencode-darwin-x64: 1.17.0 + opencode-darwin-x64-baseline: 1.17.0 + opencode-linux-arm64: 1.17.0 + opencode-linux-arm64-musl: 1.17.0 + opencode-linux-x64: 1.17.0 + opencode-linux-x64-baseline: 1.17.0 + opencode-linux-x64-baseline-musl: 1.17.0 + opencode-linux-x64-musl: 1.17.0 + opencode-windows-arm64: 1.17.0 + opencode-windows-x64: 1.17.0 + opencode-windows-x64-baseline: 1.17.0 - opencode-darwin-arm64@1.16.2: + opencode-darwin-arm64@1.17.0: optional: true - opencode-darwin-x64-baseline@1.16.2: + opencode-darwin-x64-baseline@1.17.0: optional: true - opencode-darwin-x64@1.16.2: + opencode-darwin-x64@1.17.0: optional: true - opencode-linux-arm64-musl@1.16.2: + opencode-linux-arm64-musl@1.17.0: optional: true - opencode-linux-arm64@1.16.2: + opencode-linux-arm64@1.17.0: optional: true - opencode-linux-x64-baseline-musl@1.16.2: + opencode-linux-x64-baseline-musl@1.17.0: optional: true - opencode-linux-x64-baseline@1.16.2: + opencode-linux-x64-baseline@1.17.0: optional: true - opencode-linux-x64-musl@1.16.2: + opencode-linux-x64-musl@1.17.0: optional: true - opencode-linux-x64@1.16.2: + opencode-linux-x64@1.17.0: optional: true - opencode-windows-arm64@1.16.2: + opencode-windows-arm64@1.17.0: optional: true - opencode-windows-x64-baseline@1.16.2: + opencode-windows-x64-baseline@1.17.0: optional: true - opencode-windows-x64@1.16.2: + opencode-windows-x64@1.17.0: optional: true parseurl@1.3.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ca93fb1e8d..381d72d613 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ allowBuilds: + esbuild: true opencode-ai: true diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index 77c4c10715..15f8f39335 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -32,6 +32,7 @@ skia-safe = { version = "0.93.1", default-features = false, features = [ "textlayout", "binary-cache", "webp", + "pdf", ] } thiserror = "2.0.18" uuid = { version = "1.11.0", features = ["v4", "js"] } diff --git a/render-wasm/README.md b/render-wasm/README.md index e433b27cd1..b1010bb70c 100644 --- a/render-wasm/README.md +++ b/render-wasm/README.md @@ -52,6 +52,7 @@ cd penpot/render-wasm ## Technical documentation +- [Rendering Architecture (Live vs Vector/PDF)](./docs/rendering_architecture.md) - [Serialization](./docs/serialization.md) - [Tile Rendering](./docs/tile_rendering.md) - [Texts](./docs/texts.md) diff --git a/render-wasm/docs/rendering_architecture.md b/render-wasm/docs/rendering_architecture.md new file mode 100644 index 0000000000..709c882900 --- /dev/null +++ b/render-wasm/docs/rendering_architecture.md @@ -0,0 +1,131 @@ +# Rendering Architecture: Live (GPU) vs Vector (PDF) Export + +Penpot's WASM engine has **two render paths** that must produce the same picture: + +| Path | Purpose | Backend | Code | +|------|---------|---------|------| +| **Live / GPU** | On-screen workspace, thumbnails, PNG export | WebGL surfaces + Skia | `render.rs::render_shape` (+ `render/{fills,strokes,shadows,text,...}.rs`) | +| **Vector** | True vector PDF (and future SVG) export | Single CPU Skia canvas (no GPU) | `render/vector.rs` → `render/pdf.rs` | + +They share the same shape tree and the same low-level drawing primitives, but +compose them differently. Keeping them in sync is the whole game — see +[Parity guards](#parity-guards). + +## Why two paths? + +The live path draws each shape into **many intermediate GPU surfaces** (fills, +strokes, shadows, …) and composites them. Compositing rasterises. That is fine +for the screen and for PNG, but a PDF made that way would be a bitmap. + +The vector path bypasses the GPU surface system and draws **directly onto a +Skia PDF canvas**, so paths, text and fills come out as real PDF vector +operations. Only inherently pixel-based effects (blur, blurred shadows) are +rasterised — by Skia's PDF backend, by design. + +## The two pipelines + +```mermaid +flowchart TB + tree["Shape tree (ShapesPool)"] + + subgraph GPU["Live / GPU path — render.rs"] + direction TB + g0["render_shape(shape)"] + g1["fast_mode? can_render_directly?<br/>tiles, clip stacks, nested fills/blurs"] + gF["fills::render → Surface::Fills"] + gS["strokes::render → Surface::Strokes"] + gI["shadows::* → Surface::InnerShadows"] + gD["drop shadows (tree level)<br/>→ Surface::DropShadows"] + gC["draw_shape_surface_stack_into<br/>composite surfaces → final z-order"] + g0 --> g1 --> gF --> gS --> gI --> gC + gD --> gC + end + + subgraph SHARED["Shared primitives (one source of truth)"] + p1["draw_stroke_on_rect / draw_stroke_on_circle"] + p2["handle_stroke_caps (arrows, markers)"] + p3["render_inner_stroke / render_overlay_emoji (text)"] + end + + subgraph VEC["Vector path — render/vector.rs"] + direction TB + v0["render_to_pdf → render_tree(shape)"] + v1["render_group / render_frame / render_leaf<br/>concat centered_transform, save_layer for opacity/blur"] + v2["draw_drop_shadows (inline)"] + v3["render_leaf_content<R: ShapeRenderer><br/>fills → fill inner shadows → strokes → stroke inner shadows"] + v4["one Skia PDF canvas<br/>final z = draw call order"] + v0 --> v1 --> v2 --> v3 --> v4 + end + + tree --> g0 + tree --> v0 + + gS -.uses.-> SHARED + v3 -.uses.-> SHARED +``` + +### Key differences + +| Aspect | Live / GPU | Vector | +|--------|-----------|--------| +| Drawing target | Many GPU surfaces, then composited | One Skia PDF canvas | +| Final z-order | Surface composite order (`draw_shape_surface_stack_into`) | Order of draw calls | +| Drop shadows | Rendered at tree level into a separate surface (`render_element_drop_shadows_and_composite`) | Drawn inline per shape/container (`draw_drop_shadows` / `render_container_drop_shadows`) | +| Images | GPU textures | CPU image copies (`get_cpu_image`) | +| Blur / blurred shadow | GPU filter passes | Rasterised by Skia's PDF backend | +| Perf machinery | tiles, `fast_mode`, `can_render_directly` | none (one-shot export) | + +## Export wiring (single vs multiple) + +The client-side WASM export — rendering in the browser through the vector path +(`render_shape_pdf` / `render_shape_pixels`) — is wired **only for single +exports** (`request-simple-export` in `frontend/.../exports/assets.cljs`), and +only when render-wasm is active and the `:wasm-export` flag is set. + +**Multiple/batch export** (`request-multiple-export`) always runs **server-side** +via the `:export-shapes` command; it merely passes an `:is-wasm` hint so the +server can use its own WASM renderer. So everything documented here (vector PDF, +the fixes, parity) applies to single export only. + +## Parity guards + +Three compile-time guards plus shared code keep the two paths from drifting. +The contract is documented on the `ShapeRenderer` trait +(`render/shape_renderer.rs`). + +1. **Capability guard.** `ShapeRenderer` is the single declaration of per-shape + rendering capabilities (`draw_fills`, `draw_strokes`, `draw_drop_shadows`, + …). A new effect MUST be added as a trait method, not inline in + `render_shape`. Adding a method fails to compile until the vector backend + handles it — so a feature can never be silently missing from PDF. +2. **Type guard.** Every `match` on `shape.shape_type` in `vector.rs` is + exhaustive (no `_ =>`). A new `Type` variant fails to compile until handled. +3. **Order guard.** Leaf content draw order/gating lives in exactly one place: + `vector::render_leaf_content<R: ShapeRenderer>`. It is generic over the + trait so the GPU backend could reuse it verbatim once it implements + `ShapeRenderer`. +4. **Shared primitives.** Prefer reusing the live-render functions over + mirroring them: `draw_stroke_on_rect`, `draw_stroke_on_circle`, + `handle_stroke_caps`, `render_inner_stroke`, `render_overlay_emoji`. + Whatever is still duplicated is the remaining drift surface. + +### Not yet done — full unification + +The end goal is for `render_shape` to also implement `ShapeRenderer` and route +its leaf rendering through `render_leaf_content`, so both paths share order and +gating by construction. This is a large refactor of the live hot path (tiles, +`fast_mode`, surface compositing, tree-level drop shadows) and **should be +gated by pixel parity tests** (a vector-vs-GPU raster diff harness lives on a +separate branch) — do not refactor the live path without that safety net. + +## File map + +| What | Where | +|------|-------| +| Vector entry / PDF | `render/pdf.rs`, `render/vector.rs` | +| Parity trait | `render/shape_renderer.rs` | +| Order seam | `render/vector.rs::render_leaf_content` | +| Live shape render | `render.rs::render_shape` | +| Surface compositing | `render.rs::draw_shape_surface_stack_into` | +| Shared stroke geometry / caps | `render/strokes.rs` | +| Shared text render | `render/text.rs` | diff --git a/render-wasm/src/globals.rs b/render-wasm/src/globals.rs index 0c9008c005..64e7d8c94f 100644 --- a/render-wasm/src/globals.rs +++ b/render-wasm/src/globals.rs @@ -5,7 +5,7 @@ use crate::emscripten::init_gl; use crate::mem; use crate::render::{gpu_state::GpuState, RenderState}; -use crate::state::{State, TextEditorState}; +use crate::state::{State, TextEditorState, UIState}; static mut DESIGN_STATE: *mut State = std::ptr::null_mut(); @@ -50,6 +50,17 @@ pub(crate) fn get_text_editor_state() -> &'static mut TextEditorState { } } +/// UI State +static mut UI_STATE: *mut UIState = std::ptr::null_mut(); + +#[inline(always)] +pub(crate) fn get_ui_state() -> &'static mut UIState { + unsafe { + debug_assert!(!UI_STATE.is_null(), "UI State is null"); + &mut *UI_STATE + } +} + // FIXME: These with_state* macros should be using our CriticalError instead of expect. // But to do that, we need to not use them at domain-level (i.e. in business logic), just // in the context of the wasm call. @@ -118,6 +129,14 @@ fn text_editor_init() { } } +/// Initializes UIState. +fn ui_init() { + unsafe { + let ui_state = UIState::new(); + UI_STATE = Box::into_raw(Box::new(ui_state)); + } +} + #[no_mangle] #[wasm_error] pub extern "C" fn init(width: i32, height: i32) -> Result<()> { @@ -127,6 +146,7 @@ pub extern "C" fn init(width: i32, height: i32) -> Result<()> { render_init(width, height); text_editor_init(); design_init(); + ui_init(); Ok(()) } diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index a0939cdd95..2f7503d181 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -9,6 +9,7 @@ mod render; mod shapes; mod state; mod tiles; +mod ui; mod utils; mod uuid; mod view; @@ -151,6 +152,31 @@ pub extern "C" fn render_blurred_snapshot(blur_radius: f32) -> Result<()> { Ok(()) } +#[no_mangle] +#[wasm_error] +pub extern "C" fn clear_render_include_filter() -> Result<()> { + with_state!(state, { + state.clear_include_filter(); + }); + Ok(()) +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_render_include_filter() -> Result<()> { + let bytes = mem::bytes(); + + let entries: Vec<Uuid> = bytes + .chunks(size_of::<<Uuid as SerializableResult>::BytesType>()) + .map(|data| Uuid::try_from(data).map_err(|e| Error::RecoverableError(e.to_string()))) + .collect::<Result<Vec<Uuid>>>()?; + + with_state!(state, { + state.set_include_filter(entries); + }); + Ok(()) +} + #[no_mangle] #[wasm_error] pub extern "C" fn render_sync() -> Result<()> { @@ -946,6 +972,22 @@ pub fn free_gpu_resources() { get_render_state().free_gpu_resources(); } +#[no_mangle] +#[wasm_error] +pub extern "C" fn render_shape_pdf(a: u32, b: u32, c: u32, d: u32, scale: f32) -> Result<*mut u8> { + let id = uuid_from_u32_quartet(a, b, c, d); + + with_state!(state, { + let data = state.render_shape_pdf(&id, scale)?; + + let len = data.len() as u32; + let mut buf = Vec::with_capacity(4 + data.len()); + buf.extend_from_slice(&len.to_le_bytes()); + buf.extend_from_slice(&data); + Ok(mem::write_bytes(buf)) + }) +} + pub fn main() { // Why an empty main? // Right now with the target `wasm32-unknown-emscripten` it is not possible diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 1f7d9842b2..f95685a4c5 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -6,13 +6,16 @@ pub mod gpu_state; pub mod grid_layout; mod images; mod options; +pub mod pdf; pub mod rulers; mod shadows; +pub mod shape_renderer; mod strokes; mod surfaces; pub mod text; pub mod text_editor; mod ui; +mod vector; use skia_safe::{self as skia, Matrix, RRect, Rect}; use std::borrow::Cow; @@ -372,6 +375,10 @@ pub(crate) struct RenderState { pub show_grid: Option<Uuid>, pub rulers: RulerState, pub focus_mode: FocusMode, + /// Viewer-only whitelist for fixed-scroll layer passes. + pub include_filter: Option<HashSet<Uuid>>, + /// Frame id passed as `base_object` for viewer renders; always traversed. + pub viewer_render_root: Option<Uuid>, pub touched_ids: HashSet<Uuid>, /// Temporary flag used for off-screen passes (drop-shadow masks, filter surfaces, etc.) /// where we must render shapes without inheriting ancestor layer blurs. Toggle it through @@ -568,6 +575,8 @@ impl RenderState { show_grid: None, rulers: RulerState::default(), focus_mode: FocusMode::new(), + include_filter: None, + viewer_render_root: None, touched_ids: HashSet::default(), ignore_nested_blurs: false, preview_mode: false, @@ -850,7 +859,16 @@ impl RenderState { /// on top of Target, then present. Backbuffer is left clean so it can be reused /// as-is across interactive-transform frames without stale overlay pixels. pub fn present_frame(&mut self, tree: ShapesPoolRef) { - self.surfaces.copy_backbuffer_to_target(); + // Viewer masked passes render a partial scene onto a transparent backbuffer. + // SrcOver would keep pass-1 pixels wherever the backbuffer stays transparent. + if self.viewer_masked_pass() { + self.surfaces.clear_target(skia::Color::TRANSPARENT); + self.surfaces.copy_backbuffer_to_target_replace(); + } else { + self.surfaces + .copy_backbuffer_to_target(self.background_color); + } + if self.options.is_debug_visible() { debug::render(self); } @@ -891,6 +909,14 @@ impl RenderState { pub fn reset_canvas(&mut self) { self.surfaces.reset(self.background_color); + self.surfaces.clear_backbuffer(self.background_color); + self.surfaces.clear_target(self.background_color); + } + + /// Drop cached tile textures before a one-shot `render_sync_shape` render. + pub fn prepare_sync_shape_render(&mut self) { + self.surfaces.clear_tile_atlas(); + self.surfaces.invalidate_tile_cache(); } /// NOTE: @@ -942,6 +968,21 @@ impl RenderState { return Ok(()); } + // Viewer masked passes render a partial scene. Reusing the tile texture cache would + // SrcOver-blend onto textures from the previous pass and leak pixels into the blob. + // `render_sync_shape` (viewer/thumbnails) uses the same direct backbuffer path. + if self.viewer_masked_pass() || self.viewer_render_root.is_some() { + // Use viewbox-aligned bounds (not grid-snapped) to match interactive-transform + // compositing and avoid a visible offset vs the DOM canvas. + let tile_rect = self.get_current_tile_bounds()?; + self.surfaces.draw_current_tile_into_backbuffer( + &tile_rect, + self.background_color, + surfaces::DrawOnCache::No, + ); + return Ok(()); + } + let fast_mode = self.options.is_fast_mode(); // Decide *now* (at the first real cache blit) whether we need to clear Cache. // This avoids clearing Cache on renders that don't actually paint tiles (e.g. hover/UI), @@ -1043,6 +1084,50 @@ impl RenderState { self.focus_mode.set_shapes(shapes); } + pub fn clear_include_filter(&mut self) { + self.include_filter = None; + } + + pub fn set_include_filter(&mut self, shapes: Vec<Uuid>) { + self.include_filter = Some(shapes.into_iter().collect()); + } + + fn viewer_masked_pass(&self) -> bool { + self.include_filter.is_some() + } + + /// True when the shape or any descendant is whitelisted. + pub fn shape_visible_in_include_filter(&self, shape_id: &Uuid, tree: ShapesPoolRef) -> bool { + let Some(ref include) = self.include_filter else { + return true; + }; + if include.contains(shape_id) { + return true; + } + let Some(shape) = tree.get(shape_id) else { + return false; + }; + shape + .children_ids_iter(false) + .any(|child_id| self.shape_visible_in_include_filter(child_id, tree)) + } + + /// When an include whitelist is active, only those ids are painted. + fn shape_should_paint_for_viewer_layer(&self, shape_id: &Uuid) -> bool { + match &self.include_filter { + Some(include) => include.contains(shape_id), + None => true, + } + } + + /// Viewer layer mask: traverse whitelisted subtrees; paint only listed ids. + pub fn shape_visible_for_viewer_layer(&self, shape_id: &Uuid, tree: ShapesPoolRef) -> bool { + if self.viewer_render_root.as_ref() == Some(shape_id) { + return true; + } + self.shape_visible_in_include_filter(shape_id, tree) + } + fn get_inherited_drop_shadows(&self) -> Option<Vec<skia_safe::Paint>> { let drop_shadows: Vec<&Shadow> = self .nested_shadows @@ -2584,8 +2669,11 @@ impl RenderState { // Strokes are drawn over children for clipped frames (all strokes), and for non-clipped // frames with inner strokes (inner strokes only — non-inner were rendered before children). - let needs_exit_strokes = element.clip() - || (matches!(element.shape_type, Type::Frame(_)) && element.has_inner_stroke()); + // Skip when focus mode excludes this subtree (focus_mode.exit runs after this, so + // is_active() still reflects this element's focus state here). + let needs_exit_strokes = self.focus_mode.is_active() + && (element.clip() + || (matches!(element.shape_type, Type::Frame(_)) && element.has_inner_stroke())); if needs_exit_strokes { let mut element_strokes: Cow<Shape> = Cow::Borrowed(element); @@ -3146,6 +3234,33 @@ impl RenderState { continue; } + if !self.shape_visible_for_viewer_layer(&node_id, tree) { + continue; + } + + // Ancestors needed to reach whitelisted descendants: traverse only. + if self.include_filter.is_some() + && self.shape_visible_for_viewer_layer(&node_id, tree) + && !self.shape_should_paint_for_viewer_layer(&node_id) + { + if element.is_recursive() { + let children_ids: Vec<_> = + element.children_ids_iter(false).copied().collect(); + let children_ids = sort_z_index(tree, element, children_ids); + for child_id in children_ids.iter() { + self.pending_nodes.push(NodeRenderState { + id: *child_id, + visited_children: false, + clip_bounds: clip_bounds.clone(), + visited_mask: false, + mask: false, + flattened: false, + }); + } + } + continue; + } + // For frames and groups, we must use extrect because they can have nested content // that extends beyond their selrect. Using selrect for early exit would incorrectly // skip frames/groups that have nested content in the current tile. @@ -3428,6 +3543,7 @@ impl RenderState { allow_stop: bool, ) -> Result<FrameType> { let mut should_stop = false; + self.viewer_render_root = base_object.copied(); let root_ids = { if let Some(shape_id) = base_object { vec![*shape_id] @@ -3443,7 +3559,10 @@ impl RenderState { if let Some(current_tile) = self.current_tile { // NOTE: For now we don't need to cover the case where the tile // is not cached because everything will be handled from draw_atlas. - if !self.surfaces.has_cached_tile_surface(current_tile) { + // Viewer masked passes (include_filter) must not reuse cached tiles from + // a previous pass; otherwise pass-1 pixels can leak into pass 2. + if self.viewer_masked_pass() || !self.surfaces.has_cached_tile_surface(current_tile) + { performance::begin_measure!("render_shape_tree::uncached"); let (is_empty, early_return) = self .render_shape_tree_partial_uncached(tree, timestamp, allow_stop, false)?; @@ -3454,6 +3573,7 @@ impl RenderState { } if early_return { + self.viewer_render_root = None; return Ok(FrameType::Partial); } performance::end_measure!("render_shape_tree::uncached"); @@ -3504,12 +3624,15 @@ impl RenderState { // empty tile. self.current_tile_had_shapes = false; + let viewer_masked_pass = self.viewer_masked_pass(); + let Some(ids) = self.tiles.get_shapes_at(next_tile) else { // If the tile is empty we do not need to render it. continue; }; - if self.surfaces.has_cached_tile_surface(next_tile) { + // Never skip based on cached surfaces during viewer masked passes. + if !viewer_masked_pass && self.surfaces.has_cached_tile_surface(next_tile) { // If the tile is cached, then we do not need to // render it. continue; @@ -3563,6 +3686,8 @@ impl RenderState { } } + self.viewer_render_root = None; + // Mark cache as valid for render_from_cache. // Only update for full-quality renders (non-fast mode). // An async render can complete while fast mode is active diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index b7a25388dc..b567829ab7 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -213,6 +213,11 @@ impl ImageStore { } } + pub fn get_cpu_image(&mut self, id: &Uuid) -> Option<Image> { + let gpu_image = self.get(id)?.clone(); + gpu_image.make_non_texture_image(self.context.as_mut()) + } + fn get_internal(&mut self, id: &Uuid, is_thumbnail: bool) -> Option<&Image> { let key = (*id, is_thumbnail); // Use entry API to mutate the HashMap in-place if needed diff --git a/render-wasm/src/render/pdf.rs b/render-wasm/src/render/pdf.rs new file mode 100644 index 0000000000..551a451c92 --- /dev/null +++ b/render-wasm/src/render/pdf.rs @@ -0,0 +1,55 @@ +use skia_safe as skia; + +use crate::error::Result; +use crate::state::ShapesPoolRef; +use crate::uuid::Uuid; + +use super::vector::{self, VectorTarget}; +use super::RenderState; + +/// Renders a shape tree to a PDF document and returns the raw PDF bytes. +/// +/// This is a dedicated vector-PDF render path that draws directly to a Skia +/// PDF canvas, bypassing the GPU surface system entirely. The result is a +/// true vector PDF — paths, text and fills are represented as PDF drawing +/// operations rather than rasterised bitmaps. Effects that are inherently +/// pixel-based (blur, shadows with blur) are rasterised internally by Skia's +/// PDF backend +pub fn render_to_pdf( + shared: &mut RenderState, + id: &Uuid, + tree: ShapesPoolRef, + scale: f32, +) -> Result<Vec<u8>> { + let shape = tree + .get(id) + .ok_or_else(|| crate::error::Error::CriticalError("Shape not found for PDF".to_string()))?; + let bounds = shape.extrect(tree, scale); + + let page_w = bounds.width() * scale; + let page_h = bounds.height() * scale; + + let mut pdf_bytes: Vec<u8> = Vec::new(); + + let metadata = skia::pdf::Metadata { + creator: "Penpot".to_string(), + producer: "Penpot (Skia PDF)".to_string(), + ..Default::default() + }; + + let document = skia::pdf::new_document(&mut pdf_bytes, Some(&metadata)); + + let mut on_page = document.begin_page((page_w, page_h), None); + + { + let page_canvas = on_page.canvas(); + page_canvas.scale((scale, scale)); + page_canvas.translate((-bounds.left(), -bounds.top())); + vector::render_tree(shared, page_canvas, id, tree, scale, VectorTarget::Pdf)?; + } + + let document = on_page.end_page(); + document.close(); + + Ok(pdf_bytes) +} diff --git a/render-wasm/src/render/shape_renderer.rs b/render-wasm/src/render/shape_renderer.rs new file mode 100644 index 0000000000..324258acf5 --- /dev/null +++ b/render-wasm/src/render/shape_renderer.rs @@ -0,0 +1,21 @@ +use crate::error::Result; +use crate::shapes::{Fill, Shape, Stroke}; + +/// Capabilities a leaf shape can render, implemented by the canvas-based vector +/// export backend (`vector::VectorRenderer`, used for PDF and future SVG). +/// +/// New per-shape features must be added as a method here (compile error until +/// the backend handles it, so nothing is silently missing from vector export); +/// draw order/gating lives once in `vector::render_leaf_content`. +pub trait ShapeRenderer { + fn draw_fills(&mut self, shape: &Shape, fills: &[Fill]) -> Result<()>; + fn draw_strokes(&mut self, shape: &Shape, strokes: &[&Stroke]) -> Result<()>; + fn draw_drop_shadows(&mut self, shape: &Shape) -> Result<()>; + fn draw_fill_inner_shadows(&mut self, shape: &Shape) -> Result<()>; + fn draw_stroke_inner_shadows(&mut self, shape: &Shape, stroke: &Stroke) -> Result<()>; + fn draw_text(&mut self, shape: &Shape) -> Result<()>; + fn draw_svg(&mut self, shape: &Shape) -> Result<()>; + /// Returns `true` if a layer was pushed; caller must `restore_blur_layer`. + fn apply_blur_layer(&mut self, shape: &Shape) -> bool; + fn restore_blur_layer(&mut self); +} diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 6f31573288..8221cad4b6 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -12,7 +12,7 @@ use crate::render::filters::compose_filters; use crate::render::{get_dest_rect, get_source_rect}; #[allow(clippy::too_many_arguments)] -fn draw_stroke_on_rect( +pub(super) fn draw_stroke_on_rect( canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, @@ -97,7 +97,7 @@ fn draw_stroke_on_rect( } #[allow(clippy::too_many_arguments)] -fn draw_stroke_on_circle( +pub(super) fn draw_stroke_on_circle( canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, @@ -288,7 +288,7 @@ fn handle_stroke_cap( } #[allow(clippy::too_many_arguments)] -fn handle_stroke_caps( +pub(super) fn handle_stroke_caps( path: &skia::Path, stroke: &Stroke, canvas: &skia::Canvas, diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 04d6948371..be7448bdf1 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -854,16 +854,47 @@ impl Surfaces { /// Copy the current `Backbuffer` contents into `Target`. /// This is a GPU→GPU copy via Skia (no ReadPixels). - pub fn copy_backbuffer_to_target(&mut self) { + /// + /// `Target` is cleared to `background` first so UI overlay pixels (guides, + /// grid) from the previous frame are fully erased. Without this, `SrcOver` + /// compositing would keep stale overlay pixels wherever the backbuffer is + /// transparent. + pub fn copy_backbuffer_to_target(&mut self, background: skia::Color) { let sampling_options = self.sampling_options; + let canvas = self.target.canvas(); + canvas.clear(background); self.backbuffer.draw( - self.target.canvas(), + canvas, (0.0, 0.0), sampling_options, Some(&skia::Paint::default()), ); } + /// Replace `Target` pixels with `Backbuffer` (Src blend). + /// + /// Used for viewer masked passes: transparent backbuffer regions must not + /// preserve prior `Target` content from an earlier pass. + pub fn copy_backbuffer_to_target_replace(&mut self) { + let sampling_options = self.sampling_options; + let mut paint = skia::Paint::default(); + paint.set_blend_mode(skia::BlendMode::Src); + self.backbuffer.draw( + self.target.canvas(), + (0.0, 0.0), + sampling_options, + Some(&paint), + ); + } + + pub fn clear_target(&mut self, color: skia::Color) { + self.target.canvas().clear(color); + } + + pub fn clear_tile_atlas(&mut self) { + self.tile_atlas.canvas().clear(skia::Color::TRANSPARENT); + } + /// Seed `Backbuffer` from `Target` (last presented frame). pub fn seed_backbuffer_from_target(&mut self) { let sampling_options = self.sampling_options; @@ -1026,6 +1057,17 @@ impl Surfaces { } } + /// Full backbuffer clear (viewer layer passes must not reuse prior pass pixels). + pub fn clear_backbuffer(&mut self, color: skia::Color) { + self.backbuffer.canvas().clear(color); + } + + pub fn clear_backbuffer_rect(&mut self, rect: skia::Rect, color: skia::Color) { + let mut paint = Paint::default(); + paint.set_color(color); + self.backbuffer.canvas().draw_rect(rect, &paint); + } + pub fn reset(&mut self, color: skia::Color) { self.canvas(SurfaceId::Fills).restore_to_count(1); self.canvas(SurfaceId::InnerShadows).restore_to_count(1); diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index ff459481f7..4f2d521179 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -1,10 +1,10 @@ -use super::{filters, RenderState, Shape, SurfaceId}; +use super::{filters, RenderState, Shape, SurfaceId, DEFAULT_EMOJI_FONT}; use crate::{ error::Result, math::Rect, shapes::{ - calculate_text_layout_data, set_paint_fill, ParagraphBuilderGroup, Stroke, StrokeKind, - TextContent, + calculate_text_layout_data, set_paint_fill, ParagraphBuilderGroup, ParagraphLayout, Stroke, + StrokeKind, TextContent, }, utils::{get_fallback_fonts, get_font_collection}, }; @@ -150,6 +150,62 @@ pub fn render_with_bounds_outset( stroke_bounds_outset: f32, fill_inset: Option<f32>, layer_opacity: Option<f32>, +) -> Result<()> { + render_with_bounds_outset_inner( + render_state, + canvas, + shape, + paragraph_builders, + surface_id, + shadow, + blur, + stroke_bounds_outset, + fill_inset, + layer_opacity, + false, + ) +} + +/// Like [`render_with_bounds_outset`] but with emoji bitmap overlay for PDF/vector export. +#[allow(clippy::too_many_arguments)] +pub fn render_with_bounds_outset_overlay_emoji( + canvas: &Canvas, + shape: &Shape, + paragraph_builders: &mut [Vec<ParagraphBuilder>], + shadow: Option<&Paint>, + blur: Option<&ImageFilter>, + stroke_bounds_outset: f32, + fill_inset: Option<f32>, + layer_opacity: Option<f32>, +) -> Result<()> { + render_with_bounds_outset_inner( + None, + Some(canvas), + shape, + paragraph_builders, + None, + shadow, + blur, + stroke_bounds_outset, + fill_inset, + layer_opacity, + true, + ) +} + +#[allow(clippy::too_many_arguments)] +fn render_with_bounds_outset_inner( + render_state: Option<&mut RenderState>, + canvas: Option<&Canvas>, + shape: &Shape, + paragraph_builders: &mut [Vec<ParagraphBuilder>], + surface_id: Option<SurfaceId>, + shadow: Option<&Paint>, + blur: Option<&ImageFilter>, + stroke_bounds_outset: f32, + fill_inset: Option<f32>, + layer_opacity: Option<f32>, + overlay_emoji: bool, ) -> Result<()> { if let Some(render_state) = render_state { let target_surface = surface_id.unwrap_or(SurfaceId::Fills); @@ -179,6 +235,7 @@ pub fn render_with_bounds_outset( Some(&blur_filter_clone), fill_inset, layer_opacity, + false, ); Ok(()) }, @@ -197,6 +254,7 @@ pub fn render_with_bounds_outset( blur, fill_inset, layer_opacity, + false, ); return Ok(()); } @@ -210,6 +268,7 @@ pub fn render_with_bounds_outset( blur, fill_inset, layer_opacity, + overlay_emoji, ); } Ok(()) @@ -241,6 +300,30 @@ pub fn render( ) } +/// Like [`render`] but rasterizes color emoji as bitmap overlays for PDF/vector export. +#[allow(clippy::too_many_arguments)] +pub fn render_overlay_emoji( + canvas: &Canvas, + shape: &Shape, + paragraph_builders: &mut [Vec<ParagraphBuilder>], + shadow: Option<&Paint>, + blur: Option<&ImageFilter>, + fill_inset: Option<f32>, + layer_opacity: Option<f32>, +) -> Result<()> { + render_with_bounds_outset_overlay_emoji( + canvas, + shape, + paragraph_builders, + shadow, + blur, + 0.0, + fill_inset, + layer_opacity, + ) +} + +#[allow(clippy::too_many_arguments)] fn render_text_on_canvas( canvas: &Canvas, shape: &Shape, @@ -249,6 +332,7 @@ fn render_text_on_canvas( blur: Option<&ImageFilter>, fill_inset: Option<f32>, layer_opacity: Option<f32>, + overlay_emoji: bool, ) { if let Some(blur_filter) = blur { let mut blur_paint = Paint::default(); @@ -260,7 +344,13 @@ fn render_text_on_canvas( if let Some(shadow_paint) = shadow { let layer_rec = SaveLayerRec::default().paint(shadow_paint); canvas.save_layer(&layer_rec); - draw_text(canvas, shape, paragraph_builders, layer_opacity); + draw_text( + canvas, + shape, + paragraph_builders, + layer_opacity, + overlay_emoji, + ); canvas.restore(); } else if let Some(eps) = fill_inset.filter(|&e| e > 0.0) { if let Some(erode) = skia_safe::image_filters::erode((eps, eps), None, None) { @@ -268,13 +358,31 @@ fn render_text_on_canvas( layer_paint.set_image_filter(erode); let layer_rec = SaveLayerRec::default().paint(&layer_paint); canvas.save_layer(&layer_rec); - draw_text(canvas, shape, paragraph_builders, layer_opacity); + draw_text( + canvas, + shape, + paragraph_builders, + layer_opacity, + overlay_emoji, + ); canvas.restore(); } else { - draw_text(canvas, shape, paragraph_builders, layer_opacity); + draw_text( + canvas, + shape, + paragraph_builders, + layer_opacity, + overlay_emoji, + ); } } else { - draw_text(canvas, shape, paragraph_builders, layer_opacity); + draw_text( + canvas, + shape, + paragraph_builders, + layer_opacity, + overlay_emoji, + ); } if blur.is_some() { @@ -289,6 +397,15 @@ fn paint_text( canvas: &Canvas, shape: &Shape, paragraph_builder_groups: &mut [Vec<ParagraphBuilder>], +) { + paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, false); +} + +fn paint_text_with_emoji_overlay( + canvas: &Canvas, + shape: &Shape, + paragraph_builder_groups: &mut [Vec<ParagraphBuilder>], + overlay_emoji: bool, ) { let text_content = shape.get_text_content(); let layout_info = @@ -296,6 +413,11 @@ fn paint_text( for para in &layout_info.paragraphs { para.paragraph.paint(canvas, (para.x, para.y)); + + if overlay_emoji { + paint_emoji_overlay(canvas, para); + } + for deco in ¶.decorations { draw_text_decorations( canvas, @@ -309,11 +431,123 @@ fn paint_text( } } +/// Rasterizes color emoji runs as bitmap overlays. Skia's PDF backend can't +/// embed COLR/CBDT color glyphs, so each emoji is drawn to a raster surface and +/// blitted; `paragraph.paint()` already wrote placeholder glyphs (keeps text +/// selectable). +fn paint_emoji_overlay(canvas: &Canvas, para: &ParagraphLayout) { + let line_metrics = para.paragraph.get_line_metrics(); + + // Rasterize at TARGET_DPI relative to the emoji's on-page size (72 user + // units = 1 inch), capped at MAX_RASTER_PX so a huge font can't allocate + // an unbounded surface. + const TARGET_DPI: f32 = 600.0; + const PDF_POINTS_PER_INCH: f32 = 72.0; + const MAX_RASTER_PX: f32 = 2048.0; + + let ctm = canvas.local_to_device_as_3x3(); + let sx = (ctm.scale_x().powi(2) + ctm.skew_y().powi(2)).sqrt(); + let sy = (ctm.skew_x().powi(2) + ctm.scale_y().powi(2)).sqrt(); + let output_scale = sx.max(sy).max(1.0); + + for line in &line_metrics { + let style_runs = line.get_style_metrics(line.start_index..line.end_index); + + // Build a list of (start, end, is_emoji) for each style run. + let mut run_info: Vec<(usize, usize, bool)> = Vec::new(); + for (i, (start_idx, _style_metric)) in style_runs.iter().enumerate() { + let end_idx = style_runs.get(i + 1).map_or(line.end_index, |next| next.0); + if *start_idx >= end_idx { + continue; + } + + let font = para.paragraph.get_font_at(*start_idx); + let family_name = font.typeface().family_name(); + + let normalized = family_name.to_lowercase().replace(' ', "-"); + let is_emoji = normalized.contains(DEFAULT_EMOJI_FONT); + run_info.push((*start_idx, end_idx, is_emoji)); + } + + // Merge consecutive emoji runs: Skia splits ZWJ sequences (e.g. 👩🏿‍🚀) + // per codepoint, but `get_rects_for_range` needs the full cluster range. + let mut merged_emoji_ranges: Vec<(usize, usize)> = Vec::new(); + for &(start, end, is_emoji) in &run_info { + if is_emoji { + if let Some(last) = merged_emoji_ranges.last_mut() { + if last.1 == start { + // Extend the previous range + last.1 = end; + continue; + } + } + merged_emoji_ranges.push((start, end)); + } + } + + for (range_start, range_end) in &merged_emoji_ranges { + // Get the bounding rects for this (possibly merged) emoji run + let rects = para.paragraph.get_rects_for_range( + *range_start..*range_end, + skia::textlayout::RectHeightStyle::Tight, + skia::textlayout::RectWidthStyle::Tight, + ); + + for text_box in &rects { + let r = &text_box.rect; + let w = r.width(); + let h = r.height(); + if w <= 0.0 || h <= 0.0 { + continue; + } + + // Render at TARGET_DPI relative to the emoji's final on-page + // size, clamped so the surface stays within MAX_RASTER_PX. + let mut raster_scale = output_scale * (TARGET_DPI / PDF_POINTS_PER_INCH); + let max_dim = w.max(h) * raster_scale; + if max_dim > MAX_RASTER_PX { + raster_scale *= MAX_RASTER_PX / max_dim; + } + let raster_w = (w * raster_scale).ceil() as i32; + let raster_h = (h * raster_scale).ceil() as i32; + + let info = skia::ImageInfo::new_n32_premul((raster_w, raster_h), None); + let Some(mut raster) = skia::surfaces::raster(&info, None, None) else { + continue; + }; + + let rc = raster.canvas(); + rc.clear(skia::Color::TRANSPARENT); + rc.scale((raster_scale, raster_scale)); + // Translate so the emoji rect origin maps to (0,0) + rc.translate((-r.left, -r.top)); + para.paragraph.paint(rc, (0.0, 0.0)); + + let image = raster.image_snapshot(); + + // Draw the rasterized emoji onto the PDF canvas at the + // correct position (paragraph offset + emoji rect origin). + let dest = skia::Rect::from_xywh(para.x + r.left, para.y + r.top, w, h); + + let sampling = skia::SamplingOptions::from(skia::CubicResampler::mitchell()); + canvas.draw_image_rect_with_sampling_options( + &image, + None, + dest, + sampling, + &Paint::default(), + ); + } + } + } +} + fn draw_text( canvas: &Canvas, shape: &Shape, paragraph_builder_groups: &mut [Vec<ParagraphBuilder>], layer_opacity: Option<f32>, + overlay_emoji: bool, ) { if let Some(opacity) = layer_opacity { let mut opacity_paint = Paint::default(); @@ -324,7 +558,7 @@ fn draw_text( canvas.save_layer(&SaveLayerRec::default()); } - paint_text(canvas, shape, paragraph_builder_groups); + paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, overlay_emoji); } /// Renders an inner stroke using mask + SrcIn + DstOver layer structure. diff --git a/render-wasm/src/render/ui.rs b/render-wasm/src/render/ui.rs index 2d48cdda6d..3b69955a82 100644 --- a/render-wasm/src/render/ui.rs +++ b/render-wasm/src/render/ui.rs @@ -1,8 +1,10 @@ use skia_safe::{self as skia, Color4f}; use super::{RenderState, ShapesPoolRef, SurfaceId}; +use crate::globals::get_ui_state; use crate::render::{grid_layout, rulers}; use crate::shapes::{Layout, Type}; +pub mod guides; pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) { let canvas = render_state.surfaces.canvas(SurfaceId::UI); @@ -63,6 +65,16 @@ pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) { let viewbox = render_state.viewbox; let ruler_state = render_state.rulers; rulers::render(canvas, viewbox, &render_state.fonts, &ruler_state); + // TODO: pass guides data here + let (horizontal, vertical) = get_ui_state().guides(); + guides::render( + canvas, + zoom, + render_state.options.dpr, + viewbox.area, + horizontal, + vertical, + ); canvas.restore(); diff --git a/render-wasm/src/render/ui/guides.rs b/render-wasm/src/render/ui/guides.rs new file mode 100644 index 0000000000..07bc5a6f8a --- /dev/null +++ b/render-wasm/src/render/ui/guides.rs @@ -0,0 +1,65 @@ +use skia_safe::{self as skia}; + +use crate::math::Rect; +use crate::ui::{Guide, GuideKind}; + +/// Renders the ruler guides overlay using the guides provided by the host +/// (ClojureScript) and stored in the render state. +pub fn render( + canvas: &skia::Canvas, + zoom: f32, + dpr: f32, + area: Rect, + horizontal: &[Guide], + vertical: &[Guide], +) { + for guide in horizontal { + render_guide(canvas, zoom, dpr, area, *guide); + } + for guide in vertical { + render_guide(canvas, zoom, dpr, area, *guide); + } +} + +pub fn render_guide(canvas: &skia::Canvas, zoom: f32, dpr: f32, area: Rect, guide: Guide) { + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_color(Into::<skia::Color>::into(guide.color)); + paint.set_alpha((0.7 * 255.0) as u8); + paint.set_stroke_width(1.0 * dpr / zoom); + // we disable antialias so the guides do not appear faint or blurry. + paint.set_anti_alias(false); + + // The guide line spans the whole viewport, but when it belongs to a board + // the solid part is clipped to that board's range (along the line + // direction). The trimmed-out parts are not drawn here; the hover/drag + // dashed decorations are rendered by the SVG overlay instead. + let (full_start, full_end) = match guide.kind { + GuideKind::Vertical(_) => (area.top, area.bottom), + GuideKind::Horizontal(_) => (area.left, area.right), + }; + + let (start, end) = match guide.frame_range { + Some((frame_start, frame_end)) => { + let (lo, hi) = if frame_start <= frame_end { + (frame_start, frame_end) + } else { + (frame_end, frame_start) + }; + (lo.max(full_start), hi.min(full_end)) + } + None => (full_start, full_end), + }; + + // The clipped range can fall entirely outside the viewport. + if start > end { + return; + } + + let (x1, y1, x2, y2) = match guide.kind { + GuideKind::Vertical(x) => (x, start, x, end), + GuideKind::Horizontal(y) => (start, y, end, y), + }; + + canvas.draw_line((x1, y1), (x2, y2), &paint); +} diff --git a/render-wasm/src/render/vector.rs b/render-wasm/src/render/vector.rs new file mode 100644 index 0000000000..721a02fbc5 --- /dev/null +++ b/render-wasm/src/render/vector.rs @@ -0,0 +1,957 @@ +use skia_safe::{self as skia, Canvas, Paint, RRect}; + +use crate::error::Result; +use crate::shapes::{ + merge_fills, radius_to_sigma, BlurType, Fill, Frame, Rect, Shape, Stroke, StrokeKind, Type, +}; +use crate::state::ShapesPoolRef; +use crate::uuid::Uuid; + +use super::shape_renderer::ShapeRenderer; +use super::text; +use super::RenderState; +use super::{get_dest_rect, get_source_rect}; + +// --------------------------------------------------------------------------- +// VectorTarget — vector export backend selector +// --------------------------------------------------------------------------- + +/// Vector export backend selector (PDF today; SVG could be added as a variant). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum VectorTarget { + Pdf, +} + +// --------------------------------------------------------------------------- +// VectorRenderer — implements ShapeRenderer for canvas-based vector export +// --------------------------------------------------------------------------- + +/// Canvas-based vector render backend (CPU Skia canvas, no GPU surfaces). +pub(super) struct VectorRenderer<'a> { + canvas: &'a Canvas, + shared: &'a mut RenderState, + scale: f32, + _target: VectorTarget, +} + +impl<'a> VectorRenderer<'a> { + pub fn new( + canvas: &'a Canvas, + shared: &'a mut RenderState, + scale: f32, + target: VectorTarget, + ) -> Self { + Self { + canvas, + shared, + scale, + _target: target, + } + } +} + +impl ShapeRenderer for VectorRenderer<'_> { + fn draw_fills(&mut self, shape: &Shape, fills: &[Fill]) -> Result<()> { + if fills.is_empty() { + return Ok(()); + } + + // Handle image fills individually + let has_image_fills = fills.iter().any(|f| matches!(f, Fill::Image(_))); + if has_image_fills { + for fill in fills.iter().rev() { + match fill { + Fill::Image(image_fill) => { + draw_image_fill(self.shared, self.canvas, shape, image_fill)?; + } + _ => { + let mut paint = fill.to_paint(&shape.selrect, true); + if let Some(filter) = shape.image_filter(1.) { + paint.set_image_filter(filter); + } + draw_shape_geometry(self.canvas, shape, &paint); + } + } + } + return Ok(()); + } + + let mut paint = merge_fills(fills, shape.selrect); + paint.set_anti_alias(true); + + if let Some(filter) = shape.image_filter(1.) { + paint.set_image_filter(filter); + } + + draw_shape_geometry(self.canvas, shape, &paint); + Ok(()) + } + + fn draw_strokes(&mut self, shape: &Shape, strokes: &[&Stroke]) -> Result<()> { + for stroke in strokes.iter().rev() { + draw_single_stroke(self.canvas, self.shared, self.scale, shape, stroke)?; + } + Ok(()) + } + + fn draw_drop_shadows(&mut self, shape: &Shape) -> Result<()> { + for shadow in shape.drop_shadows_visible() { + if let Some(filter) = shadow.get_drop_shadow_filter() { + let mut paint = Paint::default(); + paint.set_image_filter(filter); + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + self.canvas.save_layer(&layer_rec); + let mut fill_paint = Paint::default(); + fill_paint.set_anti_alias(true); + fill_paint.set_color(skia::Color::BLACK); + draw_shape_geometry(self.canvas, shape, &fill_paint); + self.canvas.restore(); + } + } + Ok(()) + } + + fn draw_fill_inner_shadows(&mut self, shape: &Shape) -> Result<()> { + if !shape.has_fills() { + return Ok(()); + } + for shadow in shape.inner_shadows_visible() { + let paint = shadow.get_inner_shadow_paint(true, shape.image_filter(1.).as_ref()); + self.canvas + .save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint)); + let mut fill_paint = Paint::default(); + fill_paint.set_anti_alias(true); + fill_paint.set_color(skia::Color::BLACK); + draw_shape_geometry(self.canvas, shape, &fill_paint); + self.canvas.restore(); + } + Ok(()) + } + + fn draw_stroke_inner_shadows(&mut self, shape: &Shape, stroke: &Stroke) -> Result<()> { + let is_open = shape.is_open(); + for shadow in shape.inner_shadows_visible() { + if let Some(filter) = shadow.get_inner_shadow_filter() { + let mut paint = stroke.to_stroked_paint( + is_open, + &shape.selrect, + shape.svg_attrs.as_ref(), + true, + ); + paint.set_image_filter(filter); + draw_shape_geometry(self.canvas, shape, &paint); + } + } + Ok(()) + } + + fn draw_text(&mut self, shape: &Shape) -> Result<()> { + let Type::Text(text_content) = &shape.shape_type else { + return Ok(()); + }; + + let text_content = text_content.new_bounds(shape.selrect()); + let mut paragraph_builders = text_content.paragraph_builder_group_from_text(None); + let blur_filter = shape.image_filter(1.); + + // Text drop shadows: one filter layer per shadow over fill + stroke + // silhouettes (mirrors GPU `render_text_shadows`). + let drop_shadows = shape.drop_shadow_paints(); + if !drop_shadows.is_empty() { + let shadow_stroke_outset = Stroke::max_bounds_width(shape.visible_strokes(), false); + let mut shadow_paragraphs = text_content.paragraph_builder_group_from_text(Some(true)); + let mut stroke_shadow_groups: Vec<(StrokeKind, _)> = shape + .visible_strokes() + .rev() + .map(|stroke| { + ( + stroke.render_kind(false), + text::stroke_paragraph_builder_group_from_text( + &text_content, + stroke, + &shape.selrect(), + Some(true), + ) + .0, + ) + }) + .collect(); + + for shadow_paint in &drop_shadows { + self.canvas + .save_layer(&skia::canvas::SaveLayerRec::default().paint(shadow_paint)); + + text::render_overlay_emoji( + self.canvas, + shape, + &mut shadow_paragraphs, + None, + blur_filter.as_ref(), + None, + None, + )?; + + for (kind, stroke_paragraphs) in &mut stroke_shadow_groups { + if *kind == StrokeKind::Inner { + // Inner stroke masked by the glyph fill (outset 0 here). + let mut mask_builders = text_content.paragraph_builder_group_opaque(); + let mut fill_builders = + text_content.paragraph_builder_group_from_text(Some(true)); + text::render_inner_stroke( + None, + Some(self.canvas), + shape, + &mut mask_builders, + stroke_paragraphs, + &mut fill_builders, + None, + blur_filter.as_ref(), + 0.0, + None, + )?; + } else { + text::render_with_bounds_outset_overlay_emoji( + self.canvas, + shape, + stroke_paragraphs, + None, + blur_filter.as_ref(), + shadow_stroke_outset, + None, + None, + )?; + } + } + + self.canvas.restore(); + } + } + + text::render_overlay_emoji( + self.canvas, + shape, + &mut paragraph_builders, + None, + blur_filter.as_ref(), + None, + None, + )?; + + // Strokes for text + let stroke_blur_outset = Stroke::max_bounds_width(shape.visible_strokes(), false); + + for stroke in shape.visible_strokes().rev() { + let (mut stroke_paragraphs, layer_opacity) = + text::stroke_paragraph_builder_group_from_text( + &text_content, + stroke, + &shape.selrect(), + None, + ); + if stroke.render_kind(false) == StrokeKind::Inner { + // Inner text stroke: clip to the glyph fill, else it bleeds out. + let mut mask_builders = text_content.paragraph_builder_group_opaque(); + let mut fill_builders = text_content.paragraph_builder_group_from_text(None); + text::render_inner_stroke( + None, + Some(self.canvas), + shape, + &mut mask_builders, + &mut stroke_paragraphs, + &mut fill_builders, + None, + blur_filter.as_ref(), + stroke_blur_outset, + layer_opacity, + )?; + } else { + text::render_with_bounds_outset_overlay_emoji( + self.canvas, + shape, + &mut stroke_paragraphs, + None, + blur_filter.as_ref(), + stroke_blur_outset, + None, + layer_opacity, + )?; + } + } + + // Inner shadows for text + let inner_shadows: Vec<_> = shape.inner_shadows_visible().collect(); + if !inner_shadows.is_empty() { + let mut shadow_paragraphs = text_content.paragraph_builder_group_from_text(Some(true)); + for shadow in &inner_shadows { + let shadow_paint = shadow.get_inner_shadow_paint(true, blur_filter.as_ref()); + text::render_overlay_emoji( + self.canvas, + shape, + &mut shadow_paragraphs, + Some(&shadow_paint), + blur_filter.as_ref(), + None, + None, + )?; + } + } + + Ok(()) + } + + fn draw_svg(&mut self, shape: &Shape) -> Result<()> { + let Type::SVGRaw(sr) = &shape.shape_type else { + return Ok(()); + }; + + if let Some(svg_transform) = shape.svg_transform() { + self.canvas.concat(&svg_transform); + } + if let Some(svg) = shape.svg.as_ref() { + svg.render(self.canvas); + } else { + let font_manager = skia::FontMgr::from(self.shared.fonts.font_provider().clone()); + if let Ok(dom) = skia::svg::Dom::from_str(&sr.content, font_manager) { + dom.render(self.canvas); + } + } + + Ok(()) + } + + fn apply_blur_layer(&mut self, shape: &Shape) -> bool { + let blur = match shape.blur { + Some(b) if !b.hidden && b.blur_type == BlurType::LayerBlur && b.value > 0.0 => b, + _ => return false, + }; + + let sigma = radius_to_sigma(blur.value * self.scale); + if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) { + let mut paint = Paint::default(); + paint.set_image_filter(filter); + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + self.canvas.save_layer(&layer_rec); + true + } else { + false + } + } + + fn restore_blur_layer(&mut self) { + self.canvas.restore(); + } +} + +// --------------------------------------------------------------------------- +// Tree traversal +// --------------------------------------------------------------------------- + +/// Depth-first render of the shape tree rooted at `id`. +pub(super) fn render_tree( + shared: &mut RenderState, + canvas: &Canvas, + id: &Uuid, + tree: ShapesPoolRef, + scale: f32, + target: VectorTarget, +) -> Result<()> { + let Some(element) = tree.get(id) else { + return Ok(()); + }; + + if element.hidden { + return Ok(()); + } + + match &element.shape_type { + Type::Group(group) => { + render_group(shared, canvas, element, group.masked, tree, scale, target)?; + } + Type::Frame(_) => { + render_frame(shared, canvas, element, tree, scale, target)?; + } + // Leaf types listed explicitly (no `_`) so a new Type must be handled. + Type::Rect(_) + | Type::Circle + | Type::Path(_) + | Type::Bool(_) + | Type::Text(_) + | Type::SVGRaw(_) => { + render_leaf(shared, canvas, element, scale, target)?; + } + } + + Ok(()) +} + +// --------------------------------------------------------------------------- +// Groups +// --------------------------------------------------------------------------- + +fn render_group( + shared: &mut RenderState, + canvas: &Canvas, + element: &Shape, + masked: bool, + tree: ShapesPoolRef, + scale: f32, + target: VectorTarget, +) -> Result<()> { + // A group has no geometry of its own and does NOT propagate a transform to + // its children: child shapes are stored in absolute coordinates and each + // applies its own `centered_transform`. (Concatenating the group transform + // here would double-apply it to children — visible on rotated/nested groups.) + canvas.save(); + + // Group drop shadow: subtree silhouette, below the opacity/clip layer. + render_container_drop_shadows(shared, canvas, element, tree, scale, target, false)?; + + // Layer for opacity / blend mode (and group-level layer blur) + let needs_layer = element.needs_layer(); + if needs_layer { + let mut paint = Paint::default(); + paint.set_blend_mode(element.blend_mode().into()); + paint.set_alpha_f(element.opacity()); + + if let Some(blur) = element + .blur + .filter(|b| !b.hidden && b.blur_type == BlurType::LayerBlur && b.value > 0.0) + { + let sigma = radius_to_sigma(blur.value * scale); + if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) { + paint.set_image_filter(filter); + } + } + + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + canvas.save_layer(&layer_rec); + } + + let children: Vec<Uuid> = element.children_ids_iter_forward(false).copied().collect(); + + if masked { + // Mirror the GPU mask: render all children (including the mask shape) + // as content, then re-draw the mask silhouette (the group's first child) + // with DstIn to clip everything to it. + let paint = Paint::default(); + canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint)); + + for child_id in &children { + render_tree(shared, canvas, child_id, tree, scale, target)?; + } + + if let Some(mask_id) = element.mask_id() { + let mut mask_paint = Paint::default(); + mask_paint.set_blend_mode(skia::BlendMode::DstIn); + canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&mask_paint)); + render_tree(shared, canvas, mask_id, tree, scale, target)?; + canvas.restore(); // mask layer + } + + canvas.restore(); // composition layer + } else { + for child_id in &children { + render_tree(shared, canvas, child_id, tree, scale, target)?; + } + } + + if needs_layer { + canvas.restore(); // opacity/blend layer + } + canvas.restore(); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Frames +// --------------------------------------------------------------------------- + +fn render_frame( + shared: &mut RenderState, + canvas: &Canvas, + element: &Shape, + tree: ShapesPoolRef, + scale: f32, + target: VectorTarget, +) -> Result<()> { + // A frame's own geometry (background, clip, strokes) is placed by its + // `centered_transform`, but — like groups — it does NOT propagate that + // transform to its children, which are stored in absolute coordinates. So + // the transform is applied only around the frame's own draws; children are + // rendered untransformed. + let matrix = element.centered_transform(); + + canvas.save(); + + // Frame drop shadow: background + subtree silhouette, below the clip layer + // so it extends outside the frame bounds. + render_container_drop_shadows(shared, canvas, element, tree, scale, target, true)?; + + let needs_layer = element.needs_layer(); + + if needs_layer { + let mut paint = Paint::default(); + paint.set_blend_mode(element.blend_mode().into()); + paint.set_alpha_f(element.opacity()); + + // Frame-level layer blur + if let Some(blur) = element + .blur + .filter(|b| !b.hidden && b.blur_type == BlurType::LayerBlur && b.value > 0.0) + { + let sigma = radius_to_sigma(blur.value * scale); + if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) { + paint.set_image_filter(filter); + } + } + + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + canvas.save_layer(&layer_rec); + } + + // Clip to frame bounds in the frame's own space, then undo the transform so + // children draw at their absolute coords while staying clipped (mirrors the + // GPU clip). Outset ~0.5px like the GPU clip to avoid an AA seam. + if element.clip_content { + canvas.concat(&matrix); + clip_to_frame_content(canvas, element, scale); + if let Some(inverse) = matrix.invert() { + canvas.concat(&inverse); + } + } + + // Frame's own fills (background) + inner shadows, in the frame's space. + if !element.fills.is_empty() { + canvas.save(); + canvas.concat(&matrix); + let mut renderer = VectorRenderer::new(canvas, shared, scale, target); + renderer.draw_fills(element, &element.fills)?; + renderer.draw_fill_inner_shadows(element)?; + canvas.restore(); + } + + // Children (absolute coords, no frame transform). + let children: Vec<Uuid> = element.children_ids_iter_forward(false).copied().collect(); + for child_id in &children { + render_tree(shared, canvas, child_id, tree, scale, target)?; + } + + // Strokes over children (clipped frames), in the frame's space. + let visible_strokes: Vec<&Stroke> = element.visible_strokes().collect(); + if !visible_strokes.is_empty() { + canvas.save(); + canvas.concat(&matrix); + let mut renderer = VectorRenderer::new(canvas, shared, scale, target); + renderer.draw_strokes(element, &visible_strokes)?; + canvas.restore(); + } + + if needs_layer { + canvas.restore(); // opacity/blend layer + } + canvas.restore(); + Ok(()) +} + +/// Drop shadows for a container: render the subtree into a drop-shadow filter +/// layer (its alpha becomes the shadow). `draw_fills` includes the frame +/// background in the silhouette. +fn render_container_drop_shadows( + shared: &mut RenderState, + canvas: &Canvas, + element: &Shape, + tree: ShapesPoolRef, + scale: f32, + target: VectorTarget, + draw_fills: bool, +) -> Result<()> { + for shadow in element.drop_shadows_visible() { + let Some(filter) = shadow.get_drop_shadow_filter() else { + continue; + }; + let mut paint = Paint::default(); + paint.set_image_filter(filter); + canvas.save_layer(&skia::canvas::SaveLayerRec::default().paint(&paint)); + + if draw_fills && !element.fills.is_empty() { + let mut renderer = VectorRenderer::new(canvas, shared, scale, target); + renderer.draw_fills(element, &element.fills)?; + } + + let children: Vec<Uuid> = element.children_ids_iter_forward(false).copied().collect(); + for child_id in &children { + render_tree(shared, canvas, child_id, tree, scale, target)?; + } + + canvas.restore(); + } + Ok(()) +} + +// --------------------------------------------------------------------------- +// Leaf shapes (Rect, Circle, Path, Bool, Text, SVGRaw) +// --------------------------------------------------------------------------- + +fn render_leaf( + shared: &mut RenderState, + canvas: &Canvas, + element: &Shape, + scale: f32, + target: VectorTarget, +) -> Result<()> { + let needs_layer = element.needs_layer(); + + let matrix = element.centered_transform(); + + canvas.save(); + canvas.concat(&matrix); + + // Layer for opacity/blend + if needs_layer { + let mut paint = Paint::default(); + paint.set_blend_mode(element.blend_mode().into()); + paint.set_alpha_f(element.opacity()); + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); + canvas.save_layer(&layer_rec); + } + + let mut renderer = VectorRenderer::new(canvas, shared, scale, target); + + // Layer blur (non-text shapes) + let blur_layer = if !matches!(element.shape_type, Type::Text(_)) { + renderer.apply_blur_layer(element) + } else { + false + }; + + renderer.draw_drop_shadows(element)?; + render_leaf_content(&mut renderer, element)?; + + if blur_layer { + renderer.restore_blur_layer(); + } + + if needs_layer { + canvas.restore(); + } + + canvas.restore(); + Ok(()) +} + +/// Single source of truth for leaf content draw order/gating (fills, inner +/// shadows, strokes), generic over [`ShapeRenderer`]. Drop shadows and layer +/// blur are excluded — they wrap the content and are sequenced per backend. +fn render_leaf_content<R: ShapeRenderer + ?Sized>(renderer: &mut R, shape: &Shape) -> Result<()> { + match &shape.shape_type { + Type::Text(_) => renderer.draw_text(shape)?, + Type::SVGRaw(_) => renderer.draw_svg(shape)?, + // Group/Frame never reach here; listed so a new Type must be handled. + Type::Rect(_) + | Type::Circle + | Type::Path(_) + | Type::Bool(_) + | Type::Group(_) + | Type::Frame(_) => { + renderer.draw_fills(shape, &shape.fills)?; + renderer.draw_fill_inner_shadows(shape)?; + + let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect(); + if !visible_strokes.is_empty() { + renderer.draw_strokes(shape, &visible_strokes)?; + + // Stroke inner shadows only when there are no fills (matches GPU). + if !shape.has_fills() { + for stroke in &visible_strokes { + renderer.draw_stroke_inner_shadows(shape, stroke)?; + } + } + } + } + } + Ok(()) +} + +// --------------------------------------------------------------------------- +// Private helpers (canvas-only) +// --------------------------------------------------------------------------- + +fn draw_image_fill( + shared: &mut RenderState, + canvas: &Canvas, + shape: &Shape, + image_fill: &crate::shapes::ImageFill, +) -> Result<()> { + // Use a CPU-backed image copy — GPU-backed images can't be drawn + // on the PDF canvas which has no GPU context. + let Some(image) = shared.images.get_cpu_image(&image_fill.id()) else { + return Ok(()); + }; + + let size = image.dimensions(); + let container = &shape.selrect; + + let src_rect = get_source_rect(size, container, image_fill); + let dest_rect = container; + + canvas.save(); + + // Clip to shape + clip_to_shape(canvas, shape, true); + + let mut paint = Paint::default(); + paint.set_anti_alias(true); + if let Some(filter) = shape.image_filter(1.) { + paint.set_image_filter(filter); + } + + canvas.draw_image_rect_with_sampling_options( + &image, + Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)), + dest_rect, + shared.sampling_options, + &paint, + ); + + canvas.restore(); + Ok(()) +} + +fn draw_single_stroke( + canvas: &Canvas, + shared: &mut RenderState, + scale: f32, + shape: &Shape, + stroke: &Stroke, +) -> Result<()> { + // Image-fill strokes: the stroke masks the visible area of the image. + if let Fill::Image(image_fill) = &stroke.fill { + return draw_image_stroke(canvas, shared, scale, shape, stroke, image_fill); + } + + draw_stroke_geometry(canvas, scale, shape, stroke, false); + Ok(()) +} + +/// Draws a stroke's geometry by shape type, kind and dash style. Rect/Circle +/// reuse the GPU stroke fns (dash/alignment parity); Path/Bool use double-width +/// + clip/clear + caps. `opaque` forces black for an image-stroke silhouette. +fn draw_stroke_geometry(canvas: &Canvas, scale: f32, shape: &Shape, stroke: &Stroke, opaque: bool) { + let svg_attrs = shape.svg_attrs.as_ref(); + let is_open = shape.is_open(); + + match &shape.shape_type { + shape_type @ (Type::Rect(_) | Type::Frame(_)) => { + let corners = shape_type.corners(); + let mut paint = stroke.to_paint(&shape.selrect, svg_attrs, true); + if opaque { + paint.set_shader(None); + paint.set_color(skia::Color::BLACK); + } + super::strokes::draw_stroke_on_rect( + canvas, + stroke, + &shape.selrect, + &corners, + &paint, + scale, + None, + None, + true, + ); + } + Type::Circle => { + let mut paint = stroke.to_paint(&shape.selrect, svg_attrs, true); + if opaque { + paint.set_shader(None); + paint.set_color(skia::Color::BLACK); + } + super::strokes::draw_stroke_on_circle( + canvas, + stroke, + &shape.selrect, + &paint, + scale, + None, + None, + true, + ); + } + Type::Path(_) | Type::Bool(_) => { + let mut paint = stroke.to_stroked_paint(is_open, &shape.selrect, svg_attrs, true); + if opaque { + paint.set_shader(None); + paint.set_color(skia::Color::BLACK); + } + draw_stroke_kind_aware(canvas, shape, stroke, &paint); + + if is_open { + if let Some(cap_path) = transformed_skia_path(shape) { + super::strokes::handle_stroke_caps( + &cap_path, stroke, canvas, is_open, &paint, None, true, + ); + } + } + } + // Text strokes go through draw_text; groups/svg never carry strokes. + Type::Text(_) | Type::SVGRaw(_) | Type::Group(_) => {} + } +} + +/// Draws a stroked `paint` honoring the stroke kind (inner clip / outer +/// layer+clear / center). +fn draw_stroke_kind_aware(canvas: &Canvas, shape: &Shape, stroke: &Stroke, paint: &Paint) { + match stroke.render_kind(shape.is_open()) { + StrokeKind::Inner => { + canvas.save(); + clip_to_shape(canvas, shape, true); + draw_shape_geometry(canvas, shape, paint); + canvas.restore(); + } + StrokeKind::Outer => { + canvas.save(); + canvas.save_layer(&skia::canvas::SaveLayerRec::default()); + draw_shape_geometry(canvas, shape, paint); + let mut clear_paint = Paint::default(); + clear_paint.set_blend_mode(skia::BlendMode::Clear); + clear_paint.set_anti_alias(true); + clear_paint.set_style(skia::PaintStyle::Fill); + draw_shape_geometry(canvas, shape, &clear_paint); + canvas.restore(); // layer + canvas.restore(); + } + StrokeKind::Center => { + draw_shape_geometry(canvas, shape, paint); + } + } +} + +/// Image-filled stroke: draw the stroke silhouette in a layer, then paint the +/// CPU image over it with `SrcIn` so only the stroke area shows the image. +fn draw_image_stroke( + canvas: &Canvas, + shared: &mut RenderState, + scale: f32, + shape: &Shape, + stroke: &Stroke, + image_fill: &crate::shapes::ImageFill, +) -> Result<()> { + let Some(image) = shared.images.get_cpu_image(&image_fill.id()) else { + return Ok(()); + }; + let size = image.dimensions(); + let container = shape.selrect; + + canvas.save(); + canvas.save_layer(&skia::canvas::SaveLayerRec::default()); + + // Opaque stroke silhouette; the SrcIn image draw below fills it. + draw_stroke_geometry(canvas, scale, shape, stroke, true); + + let mut image_paint = Paint::default(); + image_paint.set_blend_mode(skia::BlendMode::SrcIn); + image_paint.set_anti_alias(true); + if let Some(filter) = shape.image_filter(1.) { + image_paint.set_image_filter(filter); + } + + let src_rect = get_source_rect(size, &container, image_fill); + let dest_rect = get_dest_rect(&container, stroke.delta()); + canvas.draw_image_rect_with_sampling_options( + &image, + Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)), + dest_rect, + shared.sampling_options, + &image_paint, + ); + + canvas.restore(); // layer + canvas.restore(); + Ok(()) +} + +fn transformed_skia_path(shape: &Shape) -> Option<skia::Path> { + if !matches!(shape.shape_type, Type::Path(_) | Type::Bool(_)) { + return None; + } + shape.get_skia_path() +} + +// --------------------------------------------------------------------------- +// Geometry helpers +// --------------------------------------------------------------------------- + +/// Draws the shape's geometry (rect/rrect/oval/path) with the given paint. +fn draw_shape_geometry(canvas: &Canvas, shape: &Shape, paint: &Paint) { + match &shape.shape_type { + Type::Rect(_) | Type::Frame(_) => { + if let Some(corners) = shape.shape_type.corners() { + let rrect = RRect::new_rect_radii(shape.selrect, &corners); + canvas.draw_rrect(rrect, paint); + } else { + canvas.draw_rect(shape.selrect, paint); + } + } + Type::Circle => { + canvas.draw_oval(shape.selrect, paint); + } + Type::Path(_) | Type::Bool(_) => { + if let Some(path) = shape.get_skia_path() { + canvas.draw_path(&path, paint); + } + } + // Not plain geometry (drawn via draw_text / draw_svg / traversal). + Type::Text(_) | Type::SVGRaw(_) | Type::Group(_) => {} + } +} + +/// Clips the canvas to a frame's content bounds, outset by ~0.5 device px so +/// the hard (non-AA) clip edge doesn't shave off edge pixels and leave a seam. +fn clip_to_frame_content(canvas: &Canvas, shape: &Shape, scale: f32) { + let outset = 0.5 / scale.max(1e-6); + let mut rect = shape.selrect; + rect.outset((outset, outset)); + match shape.shape_type.corners() { + Some(corners) => { + let rrect = RRect::new_rect_radii(rect, &corners); + canvas.clip_rrect(rrect, skia::ClipOp::Intersect, false); + } + None => { + canvas.clip_rect(rect, skia::ClipOp::Intersect, false); + } + } +} + +/// Clips the canvas to the shape's geometry. +fn clip_to_shape(canvas: &Canvas, shape: &Shape, antialias: bool) { + let container = &shape.selrect; + match &shape.shape_type { + Type::Rect(Rect { + corners: Some(corners), + }) + | Type::Frame(Frame { + corners: Some(corners), + .. + }) => { + let rrect = RRect::new_rect_radii(*container, corners); + canvas.clip_rrect(rrect, skia::ClipOp::Intersect, antialias); + } + Type::Rect(_) | Type::Frame(_) => { + canvas.clip_rect(*container, skia::ClipOp::Intersect, antialias); + } + Type::Circle => { + let mut pb = skia::PathBuilder::new(); + pb.add_oval(*container, None, None); + canvas.clip_path(&pb.detach(), skia::ClipOp::Intersect, antialias); + } + Type::Path(_) | Type::Bool(_) => { + if let Some(path) = shape.get_skia_path() { + canvas.clip_path(&path, skia::ClipOp::Intersect, antialias); + } + } + // Fallback to the bounding rect. + Type::Text(_) | Type::SVGRaw(_) | Type::Group(_) => { + canvas.clip_rect(*container, skia::ClipOp::Intersect, antialias); + } + } +} diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index a1e79a3637..d651e3f457 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1077,6 +1077,15 @@ impl Shape { self.selrect.center() } + // TODO: This can be used in more places + pub fn centered_transform(&self) -> Matrix { + let center = self.center(); + let mut matrix = self.transform; + matrix.post_translate(center); + matrix.pre_translate(-center); + matrix + } + pub fn clip(&self) -> bool { self.clip_content } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 970c9cef1a..aa2fbd8d78 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -4,9 +4,11 @@ use std::collections::HashMap; mod rulers; mod shapes_pool; mod text_editor; +mod ui; pub use rulers::RulerState; pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef}; pub use text_editor::*; +pub use ui::UIState; use crate::error::{Error, Result}; use crate::render::FrameType; @@ -80,7 +82,9 @@ impl State { } pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<FrameType> { - get_render_state().start_render_loop(Some(id), &self.shapes, timestamp, true) + let render_state = get_render_state(); + render_state.prepare_sync_shape_render(); + render_state.start_render_loop(Some(id), &self.shapes, timestamp, true) } pub fn render_shape_pixels( @@ -92,6 +96,10 @@ impl State { get_render_state().render_shape_pixels(id, &self.shapes, scale, timestamp) } + pub fn render_shape_pdf(&mut self, id: &Uuid, scale: f32) -> Result<Vec<u8>> { + crate::render::pdf::render_to_pdf(get_render_state(), id, &self.shapes, scale) + } + pub fn start_render_loop(&mut self, timestamp: i32) -> Result<FrameType> { let render_state = get_render_state(); // If zoom changed (e.g. interrupted zoom render followed by pan), the @@ -118,6 +126,14 @@ impl State { get_render_state().set_focus_mode(shapes); } + pub fn clear_include_filter(&mut self) { + get_render_state().clear_include_filter(); + } + + pub fn set_include_filter(&mut self, shapes: Vec<Uuid>) { + get_render_state().set_include_filter(shapes); + } + pub fn init_shapes_pool(&mut self, capacity: usize) { self.shapes.initialize(capacity); } diff --git a/render-wasm/src/state/ui.rs b/render-wasm/src/state/ui.rs new file mode 100644 index 0000000000..607dc3117b --- /dev/null +++ b/render-wasm/src/state/ui.rs @@ -0,0 +1,210 @@ +use crate::ui::{Guide, GuideKind}; + +pub struct GuidePool { + horizontal: Vec<Guide>, + vertical: Vec<Guide>, +} + +impl GuidePool { + pub fn new() -> Self { + Self { + horizontal: Vec::new(), + vertical: Vec::new(), + } + } + + pub fn set(&mut self, guides: Vec<Guide>) { + self.horizontal.clear(); + self.vertical.clear(); + + for guide in guides { + match guide.kind { + GuideKind::Vertical(_) => self.vertical.push(guide), + GuideKind::Horizontal(_) => self.horizontal.push(guide), + } + } + + self.horizontal + .sort_by(|a, b| a.position().total_cmp(&b.position())); + self.vertical + .sort_by(|a, b| a.position().total_cmp(&b.position())); + } + + pub fn find_at(&self, x: f32, y: f32, zoom: f32, tolerance: f32) -> Option<&Guide> { + if zoom <= 0.0 || tolerance < 0.0 { + return None; + } + + let world_tolerance = tolerance / zoom; + let vertical = Self::find_closest_in_axis(&self.vertical, x, world_tolerance); + let horizontal = Self::find_closest_in_axis(&self.horizontal, y, world_tolerance); + + match (vertical, horizontal) { + (Some(v), Some(h)) => { + let v_dist = (v.position() - x).abs(); + let h_dist = (h.position() - y).abs(); + if v_dist <= h_dist { + Some(v) + } else { + Some(h) + } + } + (v, h) => v.or(h), + } + } + + fn find_closest_in_axis(guides: &[Guide], coord: f32, world_tolerance: f32) -> Option<&Guide> { + if guides.is_empty() { + return None; + } + + // NOTE: `partition_point` is a binary search, so this is O(log n) + let idx = guides.partition_point(|guide| guide.position() < coord); + let mut closest: Option<&Guide> = None; + let mut closest_dist = world_tolerance; + + for candidate_idx in [idx.wrapping_sub(1), idx] { + if candidate_idx < guides.len() { + let guide = &guides[candidate_idx]; + let dist = (guide.position() - coord).abs(); + if dist <= world_tolerance && dist <= closest_dist { + closest_dist = dist; + closest = Some(guide); + } + } + } + + closest + } +} + +pub struct UIState { + guides: GuidePool, + // TODO: show grid, rulers, etc. +} + +impl UIState { + pub fn new() -> Self { + Self { + guides: GuidePool::new(), + } + } + + pub fn guides(&self) -> (&Vec<Guide>, &Vec<Guide>) { + (&self.guides.horizontal, &self.guides.vertical) + } + + pub fn set_guides(&mut self, guides: Vec<Guide>) { + self.guides.set(guides); + } + + pub fn find_guide_at(&self, x: f32, y: f32, zoom: f32, tolerance: f32) -> Option<&Guide> { + self.guides.find_at(x, y, zoom, tolerance) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shapes::Color; + + fn vertical_guide(position: f32, index: usize) -> Guide { + Guide::new( + GuideKind::Vertical(position), + Color::BLACK, + Some(index), + None, + ) + } + + fn horizontal_guide(position: f32, index: usize) -> Guide { + Guide::new( + GuideKind::Horizontal(position), + Color::BLACK, + Some(index), + None, + ) + } + + fn pool_with(guides: Vec<Guide>) -> GuidePool { + let mut pool = GuidePool::new(); + pool.set(guides); + pool + } + + #[test] + fn set_replaces_existing_guides() { + let mut pool = pool_with(vec![vertical_guide(100.0, 0)]); + pool.set(vec![vertical_guide(200.0, 0)]); + + assert_eq!(pool.vertical.len(), 1); + assert_eq!(pool.vertical[0].kind, GuideKind::Vertical(200.0)); + assert!(pool.horizontal.is_empty()); + } + + #[test] + fn set_drops_removed_guides() { + let mut pool = pool_with(vec![ + vertical_guide(100.0, 0), + vertical_guide(200.0, 1), + horizontal_guide(300.0, 2), + ]); + pool.set(vec![vertical_guide(100.0, 0), horizontal_guide(300.0, 1)]); + + assert_eq!(pool.vertical.len(), 1); + assert_eq!(pool.horizontal.len(), 1); + assert_eq!(pool.vertical[0].kind, GuideKind::Vertical(100.0)); + assert_eq!(pool.horizontal[0].kind, GuideKind::Horizontal(300.0)); + } + + #[test] + fn find_at_returns_none_when_no_guides() { + let pool = GuidePool::new(); + assert!(pool.find_at(100.0, 100.0, 1.0, 8.0).is_none()); + } + + #[test] + fn find_at_finds_vertical_guide_within_tolerance() { + let pool = pool_with(vec![vertical_guide(100.0, 0)]); + let guide = pool.find_at(102.0, 50.0, 1.0, 8.0).unwrap(); + assert_eq!(guide.index, 0); + assert_eq!(guide.kind, GuideKind::Vertical(100.0)); + } + + #[test] + fn find_at_misses_vertical_guide_outside_tolerance() { + let pool = pool_with(vec![vertical_guide(100.0, 0)]); + assert!(pool.find_at(110.0, 50.0, 1.0, 8.0).is_none()); + } + + #[test] + fn find_at_finds_horizontal_guide_within_tolerance() { + let pool = pool_with(vec![horizontal_guide(200.0, 1)]); + let guide = pool.find_at(50.0, 203.0, 1.0, 8.0).unwrap(); + assert_eq!(guide.index, 1); + assert_eq!(guide.kind, GuideKind::Horizontal(200.0)); + } + + #[test] + fn find_at_picks_closest_vertical_guide() { + let pool = pool_with(vec![vertical_guide(100.0, 0), vertical_guide(105.0, 1)]); + let guide = pool.find_at(103.0, 0.0, 1.0, 8.0).unwrap(); + assert_eq!(guide.index, 1); + } + + #[test] + fn find_at_prefers_closer_guide_at_intersection() { + let pool = pool_with(vec![vertical_guide(102.0, 0), horizontal_guide(100.0, 1)]); + let guide = pool.find_at(100.0, 100.0, 1.0, 8.0).unwrap(); + assert_eq!(guide.index, 1); + assert_eq!(guide.kind, GuideKind::Horizontal(100.0)); + } + + #[test] + fn find_at_scales_tolerance_with_zoom() { + let pool = pool_with(vec![vertical_guide(100.0, 0)]); + + assert!(pool.find_at(104.0, 0.0, 2.0, 8.0).is_some()); + assert!(pool.find_at(105.0, 0.0, 2.0, 8.0).is_none()); + } +} diff --git a/render-wasm/src/ui.rs b/render-wasm/src/ui.rs new file mode 100644 index 0000000000..156b4a8748 --- /dev/null +++ b/render-wasm/src/ui.rs @@ -0,0 +1,57 @@ +use std::cmp::Ordering; + +use crate::shapes::Color; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GuideKind { + Vertical(f32), + Horizontal(f32), +} + +impl GuideKind { + fn value(self) -> f32 { + match self { + GuideKind::Vertical(x) => x, + GuideKind::Horizontal(y) => y, + } + } +} + +impl PartialOrd for GuideKind { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.value().partial_cmp(&other.value()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Guide { + pub kind: GuideKind, + pub color: Color, + /// Index of the guide in the guide list (clojure side) + pub index: usize, + /// When the guide belongs to a board, the `[start, end]` range (along the + /// guide's line direction) of that board. The guide is drawn solid only + /// within this range and trimmed outside it. `None` for free guides, which + /// span the whole viewport. + pub frame_range: Option<(f32, f32)>, +} + +impl Guide { + pub fn new( + kind: GuideKind, + color: Color, + index: Option<usize>, + frame_range: Option<(f32, f32)>, + ) -> Self { + Self { + kind, + color, + index: index.unwrap_or_default(), + frame_range, + } + } + + pub fn position(&self) -> f32 { + self.kind.value() + } +} diff --git a/render-wasm/src/wasm.rs b/render-wasm/src/wasm.rs index 2a3e641c1f..86f3101a83 100644 --- a/render-wasm/src/wasm.rs +++ b/render-wasm/src/wasm.rs @@ -13,3 +13,4 @@ pub mod svg_attrs; pub mod text; pub mod text_editor; pub mod transforms; +pub mod ui; diff --git a/render-wasm/src/wasm/ui.rs b/render-wasm/src/wasm/ui.rs new file mode 100644 index 0000000000..e71fd20388 --- /dev/null +++ b/render-wasm/src/wasm/ui.rs @@ -0,0 +1,124 @@ +use crate::mem; +use crate::with_state; +use crate::{ + error::{Error, Result}, + globals::{get_render_state, get_ui_state}, + ui::{Guide, GuideKind}, +}; +use macros::{wasm_error, ToJs}; + +const RAW_GUIDE_SIZE: usize = std::mem::size_of::<RawGuide>(); + +#[repr(u8)] +#[derive(Debug, Clone, PartialEq, Copy, ToJs)] +pub enum RawGuideKind { + Vertical = 0, + Horizontal = 1, +} + +impl From<u32> for RawGuideKind { + fn from(value: u32) -> Self { + match value { + 1 => RawGuideKind::Horizontal, + _ => RawGuideKind::Vertical, + } + } +} + +/// Flat, FFI-friendly representation of a guide. +/// +/// The layout uses only 32-bit fields so it can be written from ClojureScript +/// straight into the `HEAPU32`/`HEAPF32` views without padding surprises. +/// +/// `frame_start` / `frame_end` carry the board clip range (along the guide's +/// line direction). When the guide is not bound to a board they are `NaN`. +#[repr(C)] +#[repr(align(4))] +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct RawGuide { + kind: u32, + color: u32, + position: f32, + frame_start: f32, + frame_end: f32, +} + +impl From<RawGuide> for Guide { + fn from(value: RawGuide) -> Self { + let kind = match RawGuideKind::from(value.kind) { + RawGuideKind::Vertical => GuideKind::Vertical(value.position), + RawGuideKind::Horizontal => GuideKind::Horizontal(value.position), + }; + let frame_range = if value.frame_start.is_nan() || value.frame_end.is_nan() { + None + } else { + Some((value.frame_start, value.frame_end)) + }; + Guide::new(kind, value.color.into(), None, frame_range) + } +} + +impl From<[u8; RAW_GUIDE_SIZE]> for RawGuide { + fn from(bytes: [u8; RAW_GUIDE_SIZE]) -> Self { + unsafe { std::mem::transmute(bytes) } + } +} + +impl TryFrom<&[u8]> for RawGuide { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result<Self> { + let bytes: [u8; RAW_GUIDE_SIZE] = bytes + .try_into() + .map_err(|_| Error::CriticalError("Invalid guide data".to_string()))?; + Ok(RawGuide::from(bytes)) + } +} + +fn read_guides_from_bytes(buffer: &[u8], count: usize) -> Result<Vec<Guide>> { + buffer + .chunks_exact(RAW_GUIDE_SIZE) + .take(count) + .enumerate() + .map(|(i, bytes)| { + RawGuide::try_from(bytes).map(|raw_guide| { + let mut guide: Guide = raw_guide.into(); + guide.index = i; + guide + }) + }) + .collect::<Result<Vec<Guide>>>() +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_guides() -> Result<()> { + let bytes = mem::bytes(); + // The first 4 bytes are a header holding the number of guides. + let count = u32::from_le_bytes( + bytes + .get(0..4) + .and_then(|slice| slice.try_into().ok()) + .unwrap_or([0; 4]), + ) as usize; + let guides = read_guides_from_bytes(&bytes[4..], count)?; + get_ui_state().set_guides(guides); + + mem::free_bytes()?; + + // Guides are drawn on the UI overlay composited onto `Target`. Refresh the + // presented frame immediately so removed guides do not linger as stale pixels. + with_state!(state, { + get_render_state().present_frame(&state.shapes); + }); + + Ok(()) +} + +#[wasm_error] +#[no_mangle] +pub extern "C" fn find_guide_at(x: f32, y: f32, zoom: f32, tolerance: f32) -> Result<i32> { + Ok(get_ui_state() + .find_guide_at(x, y, zoom, tolerance) + .map(|guide| guide.index as i32) + .unwrap_or(-1)) +} diff --git a/tools/gh.py b/tools/gh.py index 135578298b..51779cefb3 100755 --- a/tools/gh.py +++ b/tools/gh.py @@ -5,18 +5,23 @@ gh.py — Multi-purpose CLI helper for penpot/penpot GitHub operations. Uses GitHub GraphQL and REST APIs via the authenticated ``gh`` CLI. Subcommands: - issues List issues in a milestone + issues List issues in a milestone (or unassigned with milestone=none) prs Fetch details for one or more PRs (by number or milestone) Usage: - python3 tools/gh.py issues <milestone-title> (default: state=closed) + python3 tools/gh.py issues <milestone-title> (default: state=closed) python3 tools/gh.py issues "2.16.0" --state all python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" + python3 tools/gh.py issues "2.16.0" --label "bug" (include only issues with label) + python3 tools/gh.py issues "2.16.0" --label "bug,regression" --exclude "no changelog" python3 tools/gh.py issues "2.16.0" --compare CHANGES.md + python3 tools/gh.py issues none (issues with no milestone) + python3 tools/gh.py issues none --label "enhancement" + python3 tools/gh.py issues none --state open python3 tools/gh.py prs 9179 9204 9311 python3 tools/gh.py prs --file prs.txt cat prs.txt | python3 tools/gh.py prs --stdin - python3 tools/gh.py prs --milestone "2.16.0" (default: state=merged) + python3 tools/gh.py prs --milestone "2.16.0" (default: state=merged) python3 tools/gh.py prs --milestone "2.16.0" --state all Prerequisites: @@ -134,6 +139,110 @@ query($owner: String!, $repo: String!, $milestone: Int!, $cursor: String) { """ +GQL_NO_MILESTONE_QUERY = """\ +query($query: String!, $cursor: String) { + search( + query: $query + type: ISSUE + first: 100 + after: $cursor + ) { + issueCount + pageInfo { hasNextPage endCursor } + nodes { + ... on Issue { + number + title + state + milestone { title } + issueType { name } + labels(first: 20) { nodes { name } } + closedByPullRequestsReferences(first: 5) { nodes { number } } + projectItems(first: 10) { + nodes { + project { title } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { + name + } + } + } + } + } + } + } +} +""" + + +def fetch_no_milestone_issues(states: str, labels: str | None = None) -> list[dict]: + """ + Fetch all issues that belong to NO milestone via paginated GraphQL search. + + Args: + states: GraphQL states enum array literal, e.g. ``"[CLOSED]"`` or ``"[OPEN CLOSED]"`` + labels: optional comma-separated labels to include (built into the search query) + + Returns: + List of {number, title, state, milestone, issue_type, labels, closing_prs, project_status} + """ + all_nodes: list[dict] = [] + cursor: str | None = None + + # Map states enum literal to search qualifiers + state_qualifiers = { + "[OPEN]": "is:open", + "[CLOSED]": "is:closed", + "[OPEN CLOSED]": "", + } + state_q = state_qualifiers.get(states, "") + label_q = "" + if labels: + for lbl in labels.split(","): + label_q += f" label:\"{lbl.strip()}\"" + search_query = f"repo:{OWNER}/{REPO_NAME} is:issue no:milestone{state_q}{label_q}".strip() + while True: + variables: dict[str, Any] = { + "query": search_query, + "cursor": cursor, + } + data = run_gh_graphql(GQL_NO_MILESTONE_QUERY, variables) + search = data["search"] + page_info = search["pageInfo"] + + for node in search["nodes"]: + if node is None: + continue + issue_type = node.get("issueType") + ms = node.get("milestone") + project_status = None + for pi in (node.get("projectItems") or {}).get("nodes") or []: + project = pi.get("project") or {} + if project.get("title") == "Main": + status_field = pi.get("fieldValueByName") or {} + project_status = status_field.get("name") + break + all_nodes.append({ + "number": node["number"], + "title": node["title"], + "state": node["state"], + "milestone": ms["title"] if ms else None, + "issue_type": issue_type["name"] if issue_type else None, + "labels": [lbl["name"] for lbl in node["labels"]["nodes"]], + "closing_prs": [pr["number"] for pr in node["closedByPullRequestsReferences"]["nodes"]], + "project_status": project_status, + }) + + total = len(all_nodes) + print(f" ... fetched {total} issues so far", file=sys.stderr) + + if not page_info["hasNextPage"]: + break + cursor = page_info["endCursor"] + + return all_nodes + + def fetch_milestone_issues(milestone_num: int, states: str) -> list[dict]: """ Fetch all issues in a milestone via paginated GraphQL. @@ -206,20 +315,25 @@ def load_existing_issue_numbers(filepath: str) -> set[int]: def cmd_issues(args: argparse.Namespace) -> None: """Handle the ``issues`` subcommand.""" - # Resolve milestone - print(f"Looking up milestone \"{args.milestone}\"...", file=sys.stderr) - ms = find_milestone(args.milestone) - print(f"Milestone #{ms['number']}: {ms['open_issues']} open, {ms['closed_issues']} closed", - file=sys.stderr) - # Map state to GraphQL enum array literal state_map = {"open": "[OPEN]", "closed": "[CLOSED]", "all": "[OPEN CLOSED]"} gql_states = state_map[args.state] - # Fetch issues - print(f"Fetching {args.state} issues via GraphQL...", file=sys.stderr) - issues = fetch_milestone_issues(ms["number"], gql_states) - print(f"Fetched {len(issues)} issues total", file=sys.stderr) + # ── No-milestone path ────────────────────────────────────────── + if args.milestone and args.milestone.lower() == "none": + print("Fetching issues with NO milestone...", file=sys.stderr) + issues = fetch_no_milestone_issues(gql_states, labels=args.label) + print(f"Fetched {len(issues)} issues total", file=sys.stderr) + + # ── Milestone path ───────────────────────────────────────────── + else: + print(f"Looking up milestone \"{args.milestone}\"...", file=sys.stderr) + ms = find_milestone(args.milestone) + print(f"Milestone #{ms['number']}: {ms['open_issues']} open, {ms['closed_issues']} closed", + file=sys.stderr) + print(f"Fetching {args.state} issues via GraphQL...", file=sys.stderr) + issues = fetch_milestone_issues(ms["number"], gql_states) + print(f"Fetched {len(issues)} issues total", file=sys.stderr) # Filter by excluded labels if args.exclude: @@ -229,6 +343,21 @@ def cmd_issues(args: argparse.Namespace) -> None: print(f"After excluding labels: {len(filtered)} issues", file=sys.stderr) issues = filtered + # Exclude issues with type "Task" (internal chores) — opt out with --include-tasks + if not args.include_tasks: + tasks = [iss for iss in issues if iss.get("issue_type") == "Task"] + if tasks: + issues = [iss for iss in issues if iss.get("issue_type") != "Task"] + print(f"After excluding Task issues: {len(issues)} issues (removed {len(tasks)}: {[t['number'] for t in tasks]})", file=sys.stderr) + + # Filter by included labels (--label) — issue must have ALL specified labels + if args.label: + inclusions = set(label.strip() for label in args.label.split(",")) + filtered = [issue for issue in issues + if all(lbl in issue["labels"] for lbl in inclusions)] + print(f"After filtering by labels: {len(filtered)} issues", file=sys.stderr) + issues = filtered + # Filter out issues with "Rejected" project status (unless --include-rejected) if not args.include_rejected: rejected = [iss for iss in issues if iss.get("project_status") == "Rejected"] @@ -464,8 +593,8 @@ def main() -> None: sub = parser.add_subparsers(dest="command", required=True, title="subcommands") # --- issues --- - p_issues = sub.add_parser("issues", help="List issues in a milestone") - p_issues.add_argument("milestone", help="Milestone title, e.g. '2.16.0'") + p_issues = sub.add_parser("issues", help="List issues in a milestone (or use 'none' for unassigned)") + p_issues.add_argument("milestone", help="Milestone title (e.g. '2.16.0') or 'none' for issues with no milestone") p_issues.add_argument( "--state", choices=["open", "closed", "all"], default="closed", help="Issue state filter (default: closed)" @@ -474,6 +603,10 @@ def main() -> None: "--exclude", "--exclude-labels", help="Comma-separated labels to exclude, e.g. 'release blocker,no changelog'" ) + p_issues.add_argument( + "--label", "--labels", + help="Comma-separated labels to include (issue must have ALL specified), e.g. 'bug' or 'bug,regression'" + ) p_issues.add_argument( "--compare", help="Path to CHANGES.md; only show issues NOT yet referenced in that file" @@ -482,6 +615,10 @@ def main() -> None: "--include-rejected", action="store_true", help="Include issues with 'Rejected' project status (excluded by default)" ) + p_issues.add_argument( + "--include-tasks", action="store_true", + help="Include issues with type 'Task' (excluded by default, they are internal chores)" + ) p_issues.set_defaults(func=cmd_issues) # --- prs ---