diff --git a/.github/actions/setup-comfyui-server/action.yml b/.github/actions/setup-comfyui-server/action.yml new file mode 100644 index 0000000000..d1aa1bd578 --- /dev/null +++ b/.github/actions/setup-comfyui-server/action.yml @@ -0,0 +1,55 @@ +name: Setup ComfyUI Server +description: 'Setup ComfyUI server for continuous integration (with ComfyUI_devtools node installed)' +inputs: + extra_server_params: + description: 'Additional parameters to pass to ComfyUI server' + required: false + default: '' + launch_server: + description: 'Whether to launch the server after setup' + required: false + default: 'false' +runs: + using: 'composite' + steps: + # Note: this workflow assume frontend repo is checked out and is built in ../dist + + # Checkout ComfyUI repo, install the dev_tools node and start server + - name: Checkout ComfyUI + uses: actions/checkout@v5 + with: + repository: 'comfyanonymous/ComfyUI' + path: 'ComfyUI' + + - name: Install ComfyUI_devtools from frontend repo + shell: bash + run: | + mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools + if ! cp -r ./tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/; then + echo "::error::Failed to copy ComfyUI_devtools from ./tools/devtools/" + echo "::error::This action assumes the ComfyUI_frontend repository is checked out in the current working directory." + echo "::error::Please ensure you have run 'actions/checkout@v5' before calling this action." + exit 1 + fi + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python requirements + shell: bash + working-directory: ComfyUI + run: | + python -m pip install --upgrade pip + pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu + pip install -r requirements.txt + pip install wait-for-it + + - name: Start ComfyUI server + if: ${{ inputs.launch_server == 'true' }} + shell: bash + working-directory: ComfyUI + run: | + python main.py --cpu --multi-user --front-end-root ../dist ${{ inputs.extra_server_params }} & + wait-for-it --service 127.0.0.1:8188 -t 600 diff --git a/.github/actions/setup-frontend/action.yml b/.github/actions/setup-frontend/action.yml index 3ebc12eb3f..6787552ea7 100644 --- a/.github/actions/setup-frontend/action.yml +++ b/.github/actions/setup-frontend/action.yml @@ -1,31 +1,16 @@ -name: Setup Frontend -description: 'Setup ComfyUI frontend development environment' +name: Setup ComfyUI Frontend +description: 'Install nodejs/pnpm/dependencies and optionally build ComfyUI_frontend' inputs: - extra_server_params: - description: 'Additional parameters to pass to ComfyUI server' + include_build_step: + description: 'Include the build step to build the frontend. Set to true for workflows that need a built frontend' required: false - default: '' + default: 'false' runs: using: 'composite' steps: - - name: Checkout ComfyUI - uses: actions/checkout@v4 - with: - repository: 'comfyanonymous/ComfyUI' - path: 'ComfyUI' - - - name: Checkout ComfyUI_frontend - uses: actions/checkout@v4 - with: - repository: 'Comfy-Org/ComfyUI_frontend' - path: 'ComfyUI_frontend' - - - name: Copy ComfyUI_devtools from frontend repo - shell: bash - run: | - mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools - cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ + # Note: this workflow assume frontend repo is checked out in the root of the workspace + # Install pnpm, Node.js, build frontend - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -36,32 +21,25 @@ runs: with: node-version: 'lts/*' cache: 'pnpm' - cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml' + cache-dependency-path: './pnpm-lock.yaml' - - name: Setup Python - uses: actions/setup-python@v4 + # Restore tool caches before running any build/lint operations + - name: Restore tool output cache + uses: actions/cache/restore@v4 with: - python-version: '3.10' + path: | + ./.cache + ./tsconfig.tsbuildinfo + key: tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}-${{ hashFiles('./src/**/*.{ts,vue,js,mts}', './*.config.*') }} + restore-keys: | + tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}- + tool-cache-${{ runner.os }}- - - name: Install Python requirements + - name: Install dependencies shell: bash - working-directory: ComfyUI - run: | - python -m pip install --upgrade pip - pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu - pip install -r requirements.txt - pip install wait-for-it + run: pnpm install --frozen-lockfile - - name: Build & Install ComfyUI_frontend + - name: Build ComfyUI_frontend + if: ${{ inputs.include_build_step == 'true' }} shell: bash - working-directory: ComfyUI_frontend - run: | - pnpm install --frozen-lockfile - pnpm build - - - name: Start ComfyUI server - shell: bash - working-directory: ComfyUI - run: | - python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist ${{ inputs.extra_server_params }} & - wait-for-it --service 127.0.0.1:8188 -t 600 \ No newline at end of file + run: pnpm build diff --git a/.github/actions/setup-playwright/action.yml b/.github/actions/setup-playwright/action.yml index ddd1a76055..89629fb2c4 100644 --- a/.github/actions/setup-playwright/action.yml +++ b/.github/actions/setup-playwright/action.yml @@ -6,7 +6,6 @@ runs: - name: Detect Playwright version id: detect-version shell: bash - working-directory: ComfyUI_frontend run: | PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version') echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT @@ -22,10 +21,8 @@ runs: if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' shell: bash run: pnpm exec playwright install chromium --with-deps - working-directory: ComfyUI_frontend - name: Install Playwright Browsers (operating system dependencies) if: steps.cache-playwright-browsers.outputs.cache-hit == 'true' shell: bash run: pnpm exec playwright install-deps - working-directory: ComfyUI_frontend \ No newline at end of file diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 08ad707274..76a9eb0f3d 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -11,6 +11,10 @@ on: pull_request: types: [labeled] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: wait-for-ci: runs-on: ubuntu-latest @@ -73,10 +77,10 @@ jobs: with: label_trigger: "claude-review" prompt: | - Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly. - - CRITICAL: You must post individual inline comments using the gh api commands shown in the file. - DO NOT create a summary comment. + Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly. + + CRITICAL: You must post individual inline comments using the gh api commands shown in the file. + DO NOT create a summary comment. Each issue must be posted as a separate inline comment on the specific line of code. anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'" @@ -86,3 +90,9 @@ jobs: COMMIT_SHA: ${{ github.event.pull_request.head.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }} REPOSITORY: ${{ github.repository }} + + - name: Remove claude-review label + if: always() + run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "claude-review" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index 1f20ab92ed..62956cadbf 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -4,6 +4,10 @@ on: pull_request: branches-ignore: [wip/*, draft/*, temp/*] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: write pull-requests: write diff --git a/.github/workflows/tests-ci.yaml b/.github/workflows/tests-ci.yaml index 47b7ee712f..c3aa236344 100644 --- a/.github/workflows/tests-ci.yaml +++ b/.github/workflows/tests-ci.yaml @@ -7,70 +7,37 @@ on: branches-ignore: [wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: setup: runs-on: ubuntu-latest outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - - name: Checkout ComfyUI + - name: Checkout repository uses: actions/checkout@v5 + + # Setup Test Environment, build frontend but do not start server yet + - name: Setup ComfyUI server + uses: ./.github/actions/setup-comfyui-server + - name: Setup frontend + uses: ./.github/actions/setup-frontend with: - repository: 'comfyanonymous/ComfyUI' - path: 'ComfyUI' - ref: master - - - name: Checkout ComfyUI_frontend - uses: actions/checkout@v5 - with: - repository: 'Comfy-Org/ComfyUI_frontend' - path: 'ComfyUI_frontend' - - - name: Copy ComfyUI_devtools from frontend repo - run: | - mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools - cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: 'pnpm' - cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml' - - - name: Cache tool outputs - uses: actions/cache@v4 - with: - path: | - ComfyUI_frontend/.cache - ComfyUI_frontend/tsconfig.tsbuildinfo - key: playwright-setup-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}-${{ hashFiles('ComfyUI_frontend/src/**/*.{ts,vue,js}', 'ComfyUI_frontend/*.config.*') }} - restore-keys: | - playwright-setup-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}- - playwright-setup-cache-${{ runner.os }}- - playwright-tools-cache-${{ runner.os }}- - - - name: Build ComfyUI_frontend - run: | - pnpm install --frozen-lockfile - pnpm build - working-directory: ComfyUI_frontend + include_build_step: true + - name: Setup Playwright + uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers + # Save the entire workspace as cache for later test jobs to restore - name: Generate cache key id: cache-key run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Save cache uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 with: - path: | - ComfyUI - ComfyUI_frontend + path: . key: comfyui-setup-${{ steps.cache-key.outputs.key }} # Sharded chromium tests @@ -85,54 +52,35 @@ jobs: shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] shardTotal: [8] steps: + # download built frontend repo from setup job - name: Wait for cache propagation run: sleep 10 - - name: Restore cached setup - uses: actions/cache/restore@v4 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 with: fail-on-cache-miss: true - path: | - ComfyUI - ComfyUI_frontend + path: . key: comfyui-setup-${{ needs.setup.outputs.cache-key }} - - name: Install pnpm - uses: pnpm/action-setup@v4 + # Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job + - name: Setup ComfyUI server + uses: ./.github/actions/setup-comfyui-server with: - version: 10 - - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - cache: 'pip' - - - name: Install requirements - run: | - python -m pip install --upgrade pip - pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu - pip install -r requirements.txt - pip install wait-for-it - working-directory: ComfyUI - - + launch_server: true + - name: Setup nodejs, pnpm, reuse built frontend + uses: ./.github/actions/setup-frontend - name: Setup Playwright - uses: ./ComfyUI_frontend/.github/actions/setup-playwright - - - name: Start ComfyUI server - run: | - python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist & - wait-for-it --service 127.0.0.1:8188 -t 600 - working-directory: ComfyUI + uses: ./.github/actions/setup-playwright + # Run sharded tests and upload sharded reports - name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) id: playwright run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob - working-directory: ComfyUI_frontend env: - PLAYWRIGHT_BLOB_OUTPUT_DIR: ../blob-report + PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report - - uses: actions/upload-artifact@v4 + - name: Upload blob report + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: blob-report-chromium-${{ matrix.shardIndex }} @@ -151,45 +99,27 @@ jobs: matrix: browser: [chromium-2x, chromium-0.5x, mobile-chrome] steps: + # download built frontend repo from setup job - name: Wait for cache propagation run: sleep 10 - - name: Restore cached setup - uses: actions/cache/restore@v4 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 with: fail-on-cache-miss: true - path: | - ComfyUI - ComfyUI_frontend + path: . key: comfyui-setup-${{ needs.setup.outputs.cache-key }} - - name: Install pnpm - uses: pnpm/action-setup@v4 + # Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job + - name: Setup ComfyUI server + uses: ./.github/actions/setup-comfyui-server with: - version: 10 - - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - cache: 'pip' - - - name: Install requirements - run: | - python -m pip install --upgrade pip - pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu - pip install -r requirements.txt - pip install wait-for-it - working-directory: ComfyUI - + launch_server: true + - name: Setup nodejs, pnpm, reuse built frontend + uses: ./.github/actions/setup-frontend - name: Setup Playwright - uses: ./ComfyUI_frontend/.github/actions/setup-playwright - - - name: Start ComfyUI server - run: | - python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist & - wait-for-it --service 127.0.0.1:8188 -t 600 - working-directory: ComfyUI + uses: ./.github/actions/setup-playwright + # Run tests and upload reports - name: Run Playwright tests (${{ matrix.browser }}) id: playwright run: | @@ -199,13 +129,13 @@ jobs: --reporter=list \ --reporter=html \ --reporter=json - working-directory: ComfyUI_frontend - - uses: actions/upload-artifact@v4 + - name: Upload Playwright report + uses: actions/upload-artifact@v4 if: always() with: name: playwright-report-${{ matrix.browser }} - path: ComfyUI_frontend/playwright-report/ + path: ./playwright-report/ retention-days: 30 # Merge sharded test reports @@ -214,32 +144,19 @@ jobs: runs-on: ubuntu-latest if: ${{ always() && !cancelled() }} steps: - - name: Checkout ComfyUI_frontend + - name: Checkout repository uses: actions/checkout@v5 - with: - repository: 'Comfy-Org/ComfyUI_frontend' - path: 'ComfyUI_frontend' - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: 'pnpm' - cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml' - - - name: Install dependencies - run: | - pnpm install --frozen-lockfile - working-directory: ComfyUI_frontend + # Setup Test Environment, we only need playwright to merge reports + - name: Setup frontend + uses: ./.github/actions/setup-frontend + - name: Setup Playwright + uses: ./.github/actions/setup-playwright - name: Download blob reports uses: actions/download-artifact@v4 with: - path: ComfyUI_frontend/all-blob-reports + path: ./all-blob-reports pattern: blob-report-chromium-* merge-multiple: true @@ -250,7 +167,6 @@ jobs: # Generate JSON report separately with explicit output path PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ pnpm exec playwright merge-reports --reporter=json ./all-blob-reports - working-directory: ComfyUI_frontend - name: Build failed screenshot manifest if: ${{ needs.playwright-tests-chromium-sharded.result == 'failure' }} @@ -276,7 +192,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: playwright-report-chromium - path: ComfyUI_frontend/playwright-report/ + path: ./playwright-report/ retention-days: 30 #### BEGIN Deployment and commenting (non-forked PRs only) @@ -292,11 +208,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 - + - name: Get start time id: start-time run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT - + - name: Post starting comment env: GITHUB_TOKEN: ${{ github.token }} @@ -307,7 +223,7 @@ jobs: "${{ github.head_ref }}" \ "starting" \ "${{ steps.start-time.outputs.time }}" - + # Deploy and comment for non-forked PRs only deploy-and-comment: needs: [playwright-tests, merge-reports] @@ -319,23 +235,20 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 - + - name: Download all playwright reports uses: actions/download-artifact@v4 with: pattern: playwright-report-* path: reports - - - name: Make deployment script executable - run: chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh - + - name: Deploy reports and comment on PR env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} GITHUB_TOKEN: ${{ github.token }} run: | - ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ + bash ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ "${{ github.event.pull_request.number }}" \ "${{ github.head_ref }}" \ "completed" diff --git a/.github/workflows/update-locales-for-given-custom-node-repository.yaml b/.github/workflows/update-locales-for-given-custom-node-repository.yaml index ec085eab59..b9d1b33b91 100644 --- a/.github/workflows/update-locales-for-given-custom-node-repository.yaml +++ b/.github/workflows/update-locales-for-given-custom-node-repository.yaml @@ -21,90 +21,64 @@ jobs: update-locales: runs-on: ubuntu-latest steps: - - name: Checkout ComfyUI + - name: Checkout repository uses: actions/checkout@v5 + + # Setup playwright environment with custom node repository + - name: Setup ComfyUI Server (without launching) + uses: ./.github/actions/setup-comfyui-server + - name: Setup frontend + uses: ./.github/actions/setup-frontend with: - repository: comfyanonymous/ComfyUI - path: ComfyUI - ref: master - - name: Checkout ComfyUI_frontend - uses: actions/checkout@v5 - with: - repository: Comfy-Org/ComfyUI_frontend - path: ComfyUI_frontend - - name: Copy ComfyUI_devtools from frontend repo - run: | - mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools - cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ + include_build_step: 'true' + - name: Setup Playwright + uses: ./.github/actions/setup-playwright + + # Install the custom node repository - name: Checkout custom node repository uses: actions/checkout@v5 with: repository: ${{ inputs.owner }}/${{ inputs.repository }} path: 'ComfyUI/custom_nodes/${{ inputs.repository }}' - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - cache: 'pnpm' - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Install ComfyUI requirements - run: | - python -m pip install --upgrade pip - pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu - pip install -r requirements.txt - pip install wait-for-it - working-directory: ComfyUI - - name: Install custom node requirements + - name: Install custom node Python requirements + working-directory: ComfyUI/custom_nodes/${{ inputs.repository }} run: | if [ -f "requirements.txt" ]; then pip install -r requirements.txt fi - working-directory: ComfyUI/custom_nodes/${{ inputs.repository }} - - name: Build & Install ComfyUI_frontend - run: | - pnpm install --frozen-lockfile - pnpm build - rm -rf ../ComfyUI/web/* - mv dist/* ../ComfyUI/web/ - working-directory: ComfyUI_frontend - - name: Start ComfyUI server - run: | - python main.py --cpu --multi-user & - wait-for-it --service 127.0.0.1:8188 -t 600 + + # Start ComfyUI Server + - name: Start ComfyUI Server + shell: bash working-directory: ComfyUI - - name: Setup Playwright - uses: ./ComfyUI_frontend/.github/actions/setup-playwright + run: | + python main.py --cpu --multi-user --front-end-root ../dist --custom-node-path ../ComfyUI/custom_nodes/${{ inputs.repository }} & + wait-for-it --service + - name: Start dev server # Run electron dev server as it is a superset of the web dev server # We do want electron specific UIs to be translated. run: pnpm dev:electron & - working-directory: ComfyUI_frontend + - name: Capture base i18n run: pnpm exec tsx scripts/diff-i18n capture - working-directory: ComfyUI_frontend - name: Update en.json run: pnpm collect-i18n env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - working-directory: ComfyUI_frontend - name: Update translations run: pnpm locale env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - working-directory: ComfyUI_frontend - name: Diff base vs updated i18n run: pnpm exec tsx scripts/diff-i18n diff - working-directory: ComfyUI_frontend - name: Update i18n in custom node repository run: | LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/ install -d "$LOCALE_DIR" cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR" + + # Git ops for pushing changes and creating PR - name: Check and create fork of custom node repository run: | # Try to fork the repository diff --git a/.github/workflows/update-locales.yaml b/.github/workflows/update-locales.yaml index 6ee0933121..9ffa702cae 100644 --- a/.github/workflows/update-locales.yaml +++ b/.github/workflows/update-locales.yaml @@ -16,36 +16,33 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 - - - name: Setup Frontend + + # Setup playwright environment + - name: Setup ComfyUI Frontend uses: ./.github/actions/setup-frontend - - - name: Cache tool outputs - uses: actions/cache@v4 with: - path: | - ComfyUI_frontend/.cache - ComfyUI_frontend/.cache - key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }} - restore-keys: | - i18n-tools-cache-${{ runner.os }}- + include_build_step: true + - name: Setup ComfyUI Server + uses: ./.github/actions/setup-comfyui-server + with: + launch_server: true - name: Setup Playwright uses: ./.github/actions/setup-playwright + - name: Start dev server # Run electron dev server as it is a superset of the web dev server # We do want electron specific UIs to be translated. run: pnpm dev:electron & - working-directory: ComfyUI_frontend + + # Update locales, collect new strings and update translations using OpenAI, then commit changes - name: Update en.json run: pnpm collect-i18n env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - working-directory: ComfyUI_frontend - name: Update translations run: pnpm locale env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - working-directory: ComfyUI_frontend - name: Commit updated locales run: | git config --global user.name 'github-actions' @@ -59,4 +56,3 @@ jobs: git add src/locales/ git diff --staged --quiet || git commit -m "Update locales [skip ci]" git push origin HEAD:${{ github.head_ref }} - working-directory: ComfyUI_frontend diff --git a/.github/workflows/update-node-definitions-locales.yaml b/.github/workflows/update-node-definitions-locales.yaml index b063159ddf..ce991d09ea 100644 --- a/.github/workflows/update-node-definitions-locales.yaml +++ b/.github/workflows/update-node-definitions-locales.yaml @@ -13,24 +13,32 @@ jobs: update-locales: runs-on: ubuntu-latest steps: - - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3 + - name: Checkout repository + uses: actions/checkout@v5 + # Setup playwright environment + - name: Setup ComfyUI Server (and start) + uses: ./.github/actions/setup-comfyui-server + with: + launch_server: true + - name: Setup frontend + uses: ./.github/actions/setup-frontend + with: + include_build_step: true - name: Setup Playwright uses: ./.github/actions/setup-playwright + - name: Start dev server # Run electron dev server as it is a superset of the web dev server # We do want electron specific UIs to be translated. run: pnpm dev:electron & - working-directory: ComfyUI_frontend - name: Update en.json run: pnpm collect-i18n -- scripts/collect-i18n-node-defs.ts env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - working-directory: ComfyUI_frontend - name: Update translations run: pnpm locale env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - working-directory: ComfyUI_frontend - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e with: @@ -44,4 +52,3 @@ jobs: branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }} base: main labels: dependencies - path: ComfyUI_frontend \ No newline at end of file diff --git a/.github/workflows/update-playwright-expectations.yaml b/.github/workflows/update-playwright-expectations.yaml index ece83e3900..f9780bba39 100644 --- a/.github/workflows/update-playwright-expectations.yaml +++ b/.github/workflows/update-playwright-expectations.yaml @@ -15,12 +15,16 @@ on: issue_comment: types: [created] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest if: > ( github.event_name == 'pull_request' && github.event.label.name == 'New Browser Test Expectations' ) || - ( github.event.issue.pull_request && + ( github.event.issue.pull_request && github.event_name == 'issue_comment' && ( github.event.comment.author_association == 'OWNER' || @@ -29,17 +33,46 @@ jobs: ) && startsWith(github.event.comment.body, '/update-playwright') ) steps: + - name: Find Update Comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad + id: "find-update-comment" + with: + issue-number: ${{ github.event.number || github.event.issue.number }} + comment-author: "github-actions[bot]" + body-includes: "Updating Playwright Expectations" + + - name: Add Starting Reaction + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 + with: + comment-id: ${{ steps.find-update-comment.outputs.comment-id }} + issue-number: ${{ github.event.number || github.event.issue.number }} + body: | + Updating Playwright Expectations + edit-mode: replace + reactions: eyes + + - name: Get Branch SHA + id: "get-branch" + run: echo ::set-output name=branch::$(gh pr view $PR_NO --repo $REPO --json headRefName --jq '.headRefName') + env: + REPO: ${{ github.repository }} + PR_NO: ${{ github.event.number || github.event.issue.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Initial Checkout uses: actions/checkout@v5 - - - name: Pull Request Checkout - if: github.event.issue.pull_request && github.event_name == 'issue_comment' - run: gh pr checkout ${{ github.event.issue.number }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + ref: ${{ steps.get-branch.outputs.branch }} - name: Setup Frontend uses: ./.github/actions/setup-frontend + with: + include_build_step: true + + - name: Setup ComfyUI Server + uses: ./.github/actions/setup-comfyui-server + with: + launch_server: true - name: Setup Playwright uses: ./.github/actions/setup-playwright @@ -96,12 +129,10 @@ jobs: with: run-id: ${{ steps.locate-manifest.outputs.run_id }} name: failed-screenshot-tests - path: ComfyUI_frontend/ci-rerun + path: ci-rerun - name: Re-run failed screenshot tests and update snapshots id: playwright-tests - shell: bash - working-directory: ComfyUI_frontend continue-on-error: true run: | set -euo pipefail @@ -183,27 +214,20 @@ jobs: if: always() with: name: playwright-report - path: ComfyUI_frontend/playwright-report/ + path: ./playwright-report/ retention-days: 30 - name: Debugging info - working-directory: ComfyUI_frontend run: | - echo "Branch: ${{ github.head_ref }}" + echo "PR: ${{ github.event.issue.number }}" + echo "Branch: ${{ steps.get-branch.outputs.branch }}" git status - name: Commit updated expectations id: commit - working-directory: ComfyUI_frontend run: | git config --global user.name 'github-actions' git config --global user.email 'github-actions@github.com' - if [ "${{ github.event_name }}" = "issue_comment" ]; then - true - else - git fetch origin ${{ github.head_ref }} - git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }} - fi git add browser_tests if git diff --cached --quiet; then echo "No expectation updates detected; skipping commit." @@ -215,16 +239,11 @@ jobs: echo "count=$changed_count" >> $GITHUB_OUTPUT git commit -m "[automated] Update test expectations" - if [ "${{ github.event_name }}" = "issue_comment" ]; then - git push - else - git push origin HEAD:${{ github.head_ref }} - fi + git push origin ${{ steps.get-branch.outputs.branch }} fi - name: Generate workflow summary if: always() - working-directory: ComfyUI_frontend run: | echo "## Snapshot Update Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -248,3 +267,18 @@ jobs: echo "---" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Strategy:** Selective snapshot update (only failed tests re-run)" >> $GITHUB_STEP_SUMMARY + + - name: Add Done Reaction + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 + if: github.event_name == 'issue_comment' + with: + comment-id: ${{ steps.find-update-comment.outputs.comment-id }} + issue-number: ${{ github.event.number || github.event.issue.number }} + reactions: +1 + reactions-edit-mode: replace + + - name: Remove New Browser Test Expectations label + if: always() && github.event_name == 'pull_request' + run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "New Browser Test Expectations" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/vitest-tests.yaml b/.github/workflows/vitest-tests.yaml index 46155d9121..3941451886 100644 --- a/.github/workflows/vitest-tests.yaml +++ b/.github/workflows/vitest-tests.yaml @@ -6,6 +6,10 @@ on: pull_request: branches-ignore: [wip/*, draft/*, temp/*] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 59c9af1cd7..0d060af1f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ - Routing/i18n/entry: `src/router.ts`, `src/i18n.ts`, `src/main.ts`. - Tests: unit/component in `tests-ui/` and `src/components/**/*.{test,spec}.ts`; E2E in `browser_tests/`. - Public assets: `public/`. Build output: `dist/`. -- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.js`, `.prettierrc`. +- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.ts`, `.prettierrc`. ## Build, Test, and Development Commands - `pnpm dev`: Start Vite dev server. diff --git a/CLAUDE.md b/CLAUDE.md index 74e656f005..0b187fbfcc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -126,6 +126,5 @@ const value = api.getServerFeature('config_name', defaultValue) // Get config - NEVER use `--no-verify` flag when committing - NEVER delete or disable tests to make them pass - NEVER circumvent quality checks -- NEVER use `dark:` prefix - always use `dark-theme:` for dark mode styles, for example: `dark-theme:text-white dark-theme:bg-black` -- NEVER use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `
` - +- NEVER use `dark:` or `dark-theme:` tailwind variants. Instead use a semantic value from the `style.css` theme, e.g. `bg-node-component-surface` +- NEVER use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `
` diff --git a/CODEOWNERS b/CODEOWNERS index d3517e2ab1..d754859b10 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,3 +54,10 @@ # Translations /src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer + +# LLM Instructions (blank on purpose) +.claude/ +.cursor/ +.cursorrules +**/AGENTS.md +**/CLAUDE.md \ No newline at end of file diff --git a/browser_tests/assets/vueNodes/linked-int-widget.json b/browser_tests/assets/vueNodes/linked-int-widget.json new file mode 100644 index 0000000000..9aa7b0f9a9 --- /dev/null +++ b/browser_tests/assets/vueNodes/linked-int-widget.json @@ -0,0 +1,90 @@ +{ + "id": "95ea19ba-456c-46e8-aa40-dc3ff135b746", + "revision": 0, + "last_node_id": 11, + "last_link_id": 10, + "nodes": [ + { + "id": 10, + "type": "KSampler", + "pos": [494.3333740234375, 142.3333282470703], + "size": [444, 399], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": null + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": null + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": null + }, + { + "name": "latent_image", + "type": "LATENT", + "link": null + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 10 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": null + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [67, "randomize", 20, 8, "euler", "simple", 1] + }, + { + "id": 11, + "type": "PrimitiveInt", + "pos": [24.333343505859375, 149.6666717529297], + "size": [444, 125], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [10] + } + ], + "properties": { + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [67, "randomize"] + } + ], + "links": [[10, 11, 0, 10, 4, "INT"]], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1, + "offset": [0, 0] + }, + "frontendVersion": "1.28.6" + }, + "version": 0.4 +} diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index 19796f4c4c..d46b31a98a 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -1,6 +1,5 @@ import type { APIRequestContext, Locator, Page } from '@playwright/test' -import { expect } from '@playwright/test' -import { test as base } from '@playwright/test' +import { test as base, expect } from '@playwright/test' import dotenv from 'dotenv' import * as fs from 'fs' @@ -130,7 +129,8 @@ export class ComfyPage { // Buttons public readonly resetViewButton: Locator - public readonly queueButton: Locator + public readonly queueButton: Locator // Run button in Legacy UI + public readonly runButton: Locator // Run button (renamed "Queue" -> "Run") // Inputs public readonly workflowUploadInput: Locator @@ -165,6 +165,9 @@ export class ComfyPage { this.widgetTextBox = page.getByPlaceholder('text').nth(1) this.resetViewButton = page.getByRole('button', { name: 'Reset View' }) this.queueButton = page.getByRole('button', { name: 'Queue Prompt' }) + this.runButton = page + .getByTestId('queue-button') + .getByRole('button', { name: 'Run' }) this.workflowUploadInput = page.locator('#comfy-file-input') this.visibleToasts = page.locator('.p-toast-message:visible') @@ -1086,12 +1089,6 @@ export class ComfyPage { const targetPosition = await targetSlot.getPosition() - // Debug: Log the positions we're trying to use - console.log('Drag positions:', { - source: sourcePosition, - target: targetPosition - }) - await this.dragAndDrop(sourcePosition, targetPosition) await this.nextFrame() } diff --git a/browser_tests/fixtures/VueNodeHelpers.ts b/browser_tests/fixtures/VueNodeHelpers.ts index bc4f32452c..64c03b156a 100644 --- a/browser_tests/fixtures/VueNodeHelpers.ts +++ b/browser_tests/fixtures/VueNodeHelpers.ts @@ -119,4 +119,24 @@ export class VueNodeHelpers { await this.page.waitForSelector('[data-node-id]') } } + + /** + * Get a specific widget by node title and widget name + */ + getWidgetByName(nodeTitle: string, widgetName: string): Locator { + return this.getNodeByTitle(nodeTitle).locator( + `_vue=[widget.name="${widgetName}"]` + ) + } + + /** + * Get controls for input number widgets (increment/decrement buttons and input) + */ + getInputNumberControls(widget: Locator) { + return { + input: widget.locator('input'), + incrementButton: widget.locator('button').first(), + decrementButton: widget.locator('button').last() + } + } } diff --git a/browser_tests/tests/minimap.spec.ts b/browser_tests/tests/minimap.spec.ts index df967d911e..d1ab05fc53 100644 --- a/browser_tests/tests/minimap.spec.ts +++ b/browser_tests/tests/minimap.spec.ts @@ -66,12 +66,22 @@ test.describe('Minimap', () => { await comfyPage.nextFrame() await expect(minimapContainer).not.toBeVisible() + + // Open zoom controls dropdown again + await zoomControlsButton.click() + await comfyPage.nextFrame() + await expect(toggleButton).toContainText('Show Minimap') await toggleButton.click() await comfyPage.nextFrame() await expect(minimapContainer).toBeVisible() + + // Open zoom controls dropdown again to verify button text + await zoomControlsButton.click() + await comfyPage.nextFrame() + await expect(toggleButton).toContainText('Hide Minimap') }) diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png index 2548e66ae8..74c9f0b4b5 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 6eb1e94f50..abdc6321d1 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index ec0efa7b5e..4ad0c7c77a 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png index 02f7dfd5bd..16a40d83d1 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png index 528d65f516..15065b8f28 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts index a95f9cf19b..8989dc6329 100644 --- a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts @@ -788,4 +788,171 @@ test.describe('Vue Node Link Interaction', () => { targetSlot: 2 }) }) + + test.describe('Release actions (Shift-drop)', () => { + test('Context menu opens and endpoint is pinned on Shift-drop', async ({ + comfyPage, + comfyMouse + }) => { + await comfyPage.setSetting( + 'Comfy.LinkRelease.ActionShift', + 'context menu' + ) + + const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0] + expect(samplerNode).toBeTruthy() + + const outputCenter = await getSlotCenter( + comfyPage.page, + samplerNode.id, + 0, + false + ) + + const dropPos = { x: outputCenter.x + 180, y: outputCenter.y - 140 } + + await comfyMouse.move(outputCenter) + await comfyPage.page.keyboard.down('Shift') + try { + await comfyMouse.drag(dropPos) + await comfyMouse.drop() + } finally { + await comfyPage.page.keyboard.up('Shift').catch(() => {}) + } + + // Context menu should be visible + const contextMenu = comfyPage.page.locator('.litecontextmenu') + await expect(contextMenu).toBeVisible() + + // Pinned endpoint should not change with mouse movement while menu is open + const before = await comfyPage.page.evaluate(() => { + const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos + return Array.isArray(snap) ? [snap[0], snap[1]] : null + }) + expect(before).not.toBeNull() + + // Move mouse elsewhere and verify snap position is unchanged + await comfyMouse.move({ x: dropPos.x + 160, y: dropPos.y + 100 }) + const after = await comfyPage.page.evaluate(() => { + const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos + return Array.isArray(snap) ? [snap[0], snap[1]] : null + }) + expect(after).toEqual(before) + }) + + test('Context menu -> Search pre-filters by link type and connects after selection', async ({ + comfyPage, + comfyMouse + }) => { + await comfyPage.setSetting( + 'Comfy.LinkRelease.ActionShift', + 'context menu' + ) + await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default') + + const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0] + expect(samplerNode).toBeTruthy() + + const outputCenter = await getSlotCenter( + comfyPage.page, + samplerNode.id, + 0, + false + ) + const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 120 } + + await comfyMouse.move(outputCenter) + await comfyPage.page.keyboard.down('Shift') + try { + await comfyMouse.drag(dropPos) + await comfyMouse.drop() + } finally { + await comfyPage.page.keyboard.up('Shift').catch(() => {}) + } + + // Open Search from the context menu + await comfyPage.clickContextMenuItem('Search') + + // Search box opens with prefilled type filter based on link type (LATENT) + await expect(comfyPage.searchBox.input).toBeVisible() + const chips = comfyPage.searchBox.filterChips + // Ensure at least one filter chip exists and it matches the link type + const chipCount = await chips.count() + expect(chipCount).toBeGreaterThan(0) + await expect(chips.first()).toContainText('LATENT') + + // Choose a compatible node and verify it auto-connects + await comfyPage.searchBox.fillAndSelectFirstNode('VAEDecode') + await comfyPage.nextFrame() + + // KSampler output should now have an outgoing link + const samplerOutput = await samplerNode.getOutput(0) + expect(await samplerOutput.getLinkCount()).toBe(1) + + // One of the VAEDecode nodes should have an incoming link on input[0] + const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode') + let linked = false + for (const vae of vaeNodes) { + const details = await getInputLinkDetails(comfyPage.page, vae.id, 0) + if (details) { + expect(details.originId).toBe(samplerNode.id) + linked = true + break + } + } + expect(linked).toBe(true) + }) + + test('Search box opens on Shift-drop and connects after selection', async ({ + comfyPage, + comfyMouse + }) => { + await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box') + + const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0] + expect(samplerNode).toBeTruthy() + + const outputCenter = await getSlotCenter( + comfyPage.page, + samplerNode.id, + 0, + false + ) + const dropPos = { x: outputCenter.x + 140, y: outputCenter.y - 100 } + + await comfyMouse.move(outputCenter) + await comfyPage.page.keyboard.down('Shift') + try { + await comfyMouse.drag(dropPos) + await comfyMouse.drop() + } finally { + await comfyPage.page.keyboard.up('Shift').catch(() => {}) + } + + // Search box should open directly + await expect(comfyPage.searchBox.input).toBeVisible() + await expect(comfyPage.searchBox.filterChips.first()).toContainText( + 'LATENT' + ) + + // Select a compatible node and verify connection + await comfyPage.searchBox.fillAndSelectFirstNode('VAEDecode') + await comfyPage.nextFrame() + + const samplerOutput = await samplerNode.getOutput(0) + expect(await samplerOutput.getLinkCount()).toBe(1) + + const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode') + let linked = false + for (const vae of vaeNodes) { + const details = await getInputLinkDetails(comfyPage.page, vae.id, 0) + if (details) { + expect(details.originId).toBe(samplerNode.id) + linked = true + break + } + } + expect(linked).toBe(true) + }) + }) }) diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index eb3725e5c6..f2085123e4 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index ff85a019bb..d83bf025e6 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index b69b3d3993..b99cf7b352 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 023d075adc..9523f2f17f 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index d267102166..a811f21947 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index a0271f86b2..34c4b43593 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index 3f0d5f72a4..838996caa0 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index f1dbcf18f9..459b0eab70 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png index 6cefdc2a98..279db6bdb6 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png index 172c373fc8..dd727958e8 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/select.spec.ts b/browser_tests/tests/vueNodes/interactions/node/select.spec.ts index 2af6765896..4402236d24 100644 --- a/browser_tests/tests/vueNodes/interactions/node/select.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/node/select.spec.ts @@ -49,4 +49,36 @@ test.describe('Vue Node Selection', () => { expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(0) }) } + + test('should select pinned node without dragging', async ({ comfyPage }) => { + const PIN_HOTKEY = 'p' + const PIN_INDICATOR = '[data-testid="node-pin-indicator"]' + + // Select a node by clicking its title + const checkpointNodeHeader = comfyPage.page.getByText('Load Checkpoint') + await checkpointNodeHeader.click() + + // Pin it using the hotkey (as a user would) + await comfyPage.page.keyboard.press(PIN_HOTKEY) + + const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint') + const pinIndicator = checkpointNode.locator(PIN_INDICATOR) + await expect(pinIndicator).toBeVisible() + + expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1) + + const initialPos = await checkpointNodeHeader.boundingBox() + if (!initialPos) throw new Error('Failed to get header position') + + await comfyPage.dragAndDrop( + { x: initialPos.x + 10, y: initialPos.y + 10 }, + { x: initialPos.x + 100, y: initialPos.y + 100 } + ) + + const finalPos = await checkpointNodeHeader.boundingBox() + if (!finalPos) throw new Error('Failed to get header position after drag') + expect(finalPos).toEqual(initialPos) + + expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1) + }) }) diff --git a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts index 74ec17cc90..2d67ca99f1 100644 --- a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts +++ b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts @@ -20,6 +20,9 @@ test.describe('Vue Node Bypass', () => { const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint') await expect(checkpointNode).toHaveClass(BYPASS_CLASS) + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-bypassed-state.png' + ) await comfyPage.page.keyboard.press(BYPASS_HOTKEY) await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS) diff --git a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png new file mode 100644 index 0000000000..a4391bbe85 Binary files /dev/null and b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index 3ccb97b5a7..4e9b998d62 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index 0873ebfbbd..f4a49418cd 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 4326b8f7e7..46be17201f 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/error.spec.ts b/browser_tests/tests/vueNodes/nodeStates/error.spec.ts index f4f8e10fe0..b8c718239c 100644 --- a/browser_tests/tests/vueNodes/nodeStates/error.spec.ts +++ b/browser_tests/tests/vueNodes/nodeStates/error.spec.ts @@ -3,7 +3,7 @@ import { comfyPageFixture as test } from '../../../fixtures/ComfyPage' -const ERROR_CLASS = /border-error/ +const ERROR_CLASS = /border-node-stroke-error/ test.describe('Vue Node Error', () => { test.beforeEach(async ({ comfyPage }) => { @@ -17,16 +17,21 @@ test.describe('Vue Node Error', () => { await comfyPage.setup() await comfyPage.loadWorkflow('missing/missing_nodes') - // Close missing nodes warning dialog - await comfyPage.page.getByRole('button', { name: 'Close' }).click() - await comfyPage.page.waitForSelector('.comfy-missing-nodes', { - state: 'hidden' - }) - // Expect error state on missing unknown node const unknownNode = comfyPage.page.locator('[data-node-id]').filter({ hasText: 'UNKNOWN NODE' }) await expect(unknownNode).toHaveClass(ERROR_CLASS) }) + + test('should display error state when node causes execution error', async ({ + comfyPage + }) => { + await comfyPage.setup() + await comfyPage.loadWorkflow('nodes/execution_error') + await comfyPage.runButton.click() + + const raiseErrorNode = comfyPage.vueNodes.getNodeByTitle('Raise Error') + await expect(raiseErrorNode).toHaveClass(ERROR_CLASS) + }) }) diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index 89173c6409..25186e6e7d 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index e6281d3255..c00eddccf8 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index f05e4f68be..d83d19c86d 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts index 37dcfd37b5..3fe656ebc9 100644 --- a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts +++ b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts @@ -4,7 +4,7 @@ import { } from '../../../fixtures/ComfyPage' const MUTE_HOTKEY = 'Control+m' -const MUTE_CLASS = /opacity-50/ +const MUTE_OPACITY = '0.5' test.describe('Vue Node Mute', () => { test.beforeEach(async ({ comfyPage }) => { @@ -19,10 +19,11 @@ test.describe('Vue Node Mute', () => { await comfyPage.page.keyboard.press(MUTE_HOTKEY) const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint') - await expect(checkpointNode).toHaveClass(MUTE_CLASS) + await expect(checkpointNode).toHaveCSS('opacity', MUTE_OPACITY) + await expect(comfyPage.canvas).toHaveScreenshot('vue-node-muted-state.png') await comfyPage.page.keyboard.press(MUTE_HOTKEY) - await expect(checkpointNode).not.toHaveClass(MUTE_CLASS) + await expect(checkpointNode).not.toHaveCSS('opacity', MUTE_OPACITY) }) test('should allow toggling mute on multiple selected nodes with hotkey', async ({ @@ -35,11 +36,11 @@ test.describe('Vue Node Mute', () => { const ksamplerNode = comfyPage.vueNodes.getNodeByTitle('KSampler') await comfyPage.page.keyboard.press(MUTE_HOTKEY) - await expect(checkpointNode).toHaveClass(MUTE_CLASS) - await expect(ksamplerNode).toHaveClass(MUTE_CLASS) + await expect(checkpointNode).toHaveCSS('opacity', MUTE_OPACITY) + await expect(ksamplerNode).toHaveCSS('opacity', MUTE_OPACITY) await comfyPage.page.keyboard.press(MUTE_HOTKEY) - await expect(checkpointNode).not.toHaveClass(MUTE_CLASS) - await expect(ksamplerNode).not.toHaveClass(MUTE_CLASS) + await expect(checkpointNode).not.toHaveCSS('opacity', MUTE_OPACITY) + await expect(ksamplerNode).not.toHaveCSS('opacity', MUTE_OPACITY) }) }) diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png new file mode 100644 index 0000000000..1bc4b1edc3 Binary files /dev/null and b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts b/browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts new file mode 100644 index 0000000000..bb956e3395 --- /dev/null +++ b/browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts @@ -0,0 +1,42 @@ +import { + comfyExpect as expect, + comfyPageFixture as test +} from '../../../../fixtures/ComfyPage' + +test.describe('Vue Integer Widget', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.setup() + }) + + test('should be disabled and not allow changing value when link connected to slot', async ({ + comfyPage + }) => { + await comfyPage.loadWorkflow('vueNodes/linked-int-widget') + await comfyPage.vueNodes.waitForNodes() + + const seedWidget = comfyPage.vueNodes.getWidgetByName('KSampler', 'seed') + const controls = comfyPage.vueNodes.getInputNumberControls(seedWidget) + const initialValue = Number(await controls.input.inputValue()) + + // Verify widget is disabled when linked + await controls.incrementButton.click({ force: true }) + await expect(controls.input).toHaveValue(initialValue.toString()) + + await controls.decrementButton.click({ force: true }) + await expect(controls.input).toHaveValue(initialValue.toString()) + + await expect(seedWidget).toBeVisible() + + // Delete the node that is linked to the slot (freeing up the widget) + await comfyPage.vueNodes.getNodeByTitle('Int').click() + await comfyPage.vueNodes.deleteSelected() + + // Test widget works when unlinked + await controls.incrementButton.click() + await expect(controls.input).toHaveValue((initialValue + 1).toString()) + + await controls.decrementButton.click() + await expect(controls.input).toHaveValue(initialValue.toString()) + }) +}) diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index 6e960b8bb8..9f0cbc6498 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts b/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts new file mode 100644 index 0000000000..6f3701c127 --- /dev/null +++ b/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts @@ -0,0 +1,51 @@ +import { + comfyExpect as expect, + comfyPageFixture as test +} from '../../../fixtures/ComfyPage' + +test.describe('Vue Widget Reactivity', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.vueNodes.waitForNodes() + }) + test('Should display added widgets', async ({ comfyPage }) => { + const loadCheckpointNode = comfyPage.page.locator( + 'css=[data-testid="node-body-4"] > .lg-node-widgets > div' + ) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['4'] + node.widgets.push(node.widgets[0]) + }) + await expect(loadCheckpointNode).toHaveCount(2) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['4'] + node.widgets[2] = node.widgets[0] + }) + await expect(loadCheckpointNode).toHaveCount(3) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['4'] + node.widgets.splice(0, 0, node.widgets[0]) + }) + await expect(loadCheckpointNode).toHaveCount(4) + }) + test('Should hide removed widgets', async ({ comfyPage }) => { + const loadCheckpointNode = comfyPage.page.locator( + 'css=[data-testid="node-body-3"] > .lg-node-widgets > div' + ) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['3'] + node.widgets.pop() + }) + await expect(loadCheckpointNode).toHaveCount(5) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['3'] + node.widgets.length-- + }) + await expect(loadCheckpointNode).toHaveCount(4) + await comfyPage.page.evaluate(() => { + const node = window['graph']._nodes_by_id['3'] + node.widgets.splice(0, 1) + }) + await expect(loadCheckpointNode).toHaveCount(3) + }) +}) diff --git a/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png index fde5a2de99..89b8ae6f39 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png differ diff --git a/docs/PLAYWRIGHT_SELECTIVE_RERUN_ALTERNATIVES.md b/docs/PLAYWRIGHT_SELECTIVE_RERUN_ALTERNATIVES.md new file mode 100644 index 0000000000..0eb07eca00 --- /dev/null +++ b/docs/PLAYWRIGHT_SELECTIVE_RERUN_ALTERNATIVES.md @@ -0,0 +1,755 @@ +# Playwright Selective Test Rerun Alternatives + +This document analyzes alternatives for selectively re-running only failed Playwright tests for snapshot updates, comparing native Playwright features with the current custom manifest approach used in this project. + +## Table of Contents +- [Current Approach](#current-approach) +- [Native Playwright Features](#native-playwright-features) +- [Playwright Reporter Options](#playwright-reporter-options) +- [GitHub Actions Integration Patterns](#github-actions-integration-patterns) +- [Third-Party Solutions](#third-party-solutions) +- [Comparison and Recommendations](#comparison-and-recommendations) + +--- + +## Current Approach + +### Implementation +The project currently uses a **custom manifest-based approach** that: + +1. **Generates a manifest** of failed screenshot tests after CI runs + - Script: `scripts/cicd/build-failed-screenshot-manifest.ts` + - Parses JSON report to find tests with failed screenshot assertions + - Creates per-project text files: `ci-rerun/{project}.txt` + - Format: `file_path:line_number` (e.g., `browser_tests/menu.test.ts:42`) + +2. **Stores manifest as GitHub artifact** + - Artifact name: `failed-screenshot-tests` + - Retention: 7 days + - Only uploaded when chromium sharded tests fail + +3. **Downloads manifest in update workflow** + - Workflow: `.github/workflows/update-playwright-expectations.yaml` + - Triggered by: PR label "New Browser Test Expectations" or `/update-playwright` comment + - Falls back to full test suite if manifest not found + +4. **Re-runs only failed tests** + ```bash + for f in ci-rerun/*.txt; do + project="$(basename "$f" .txt)" + mapfile -t lines < "$f" + # Filter empty lines + pnpm exec playwright test --project="$project" --update-snapshots "${filtered[@]}" + done + ``` + +### Advantages +- ✅ Works across workflow runs and different trigger mechanisms +- ✅ Survives beyond single workflow execution +- ✅ Precise control over which tests to re-run +- ✅ Supports multiple projects with separate manifests +- ✅ Works with sharded test runs (merged report) +- ✅ Platform-agnostic approach (works on any CI/CD platform) + +### Disadvantages +- ❌ Custom implementation requires maintenance +- ❌ Requires parsing JSON report format (could break with Playwright updates) +- ❌ Additional artifact storage needed +- ❌ More complex than native solutions + +--- + +## Native Playwright Features + +### 1. `--last-failed` CLI Flag + +**Availability:** Playwright v1.44.0+ (May 2024) + +#### How It Works +```bash +# First run - execute all tests +npx playwright test + +# Second run - only re-run failed tests +npx playwright test --last-failed +``` + +Playwright maintains a `.last-run.json` file in the `test-results/` directory that tracks failed tests. + +#### CLI Examples +```bash +# Run only failed tests from last run +npx playwright test --last-failed + +# Update snapshots for only failed tests +npx playwright test --last-failed --update-snapshots + +# Combine with project filtering +npx playwright test --last-failed --project=chromium + +# Debug failed tests +npx playwright test --last-failed --debug +``` + +#### File Location and Format +- **Location:** `test-results/.last-run.json` +- **Format:** JSON object containing failed test information +- **Structure:** Contains a `failedTests: []` array with test identifiers +- **Persistence:** Cleared when all tests pass on subsequent run + +#### Advantages +- ✅ Built into Playwright (no custom code) +- ✅ Simple CLI flag +- ✅ Automatically maintained by Playwright +- ✅ Works with all Playwright features (debug, UI mode, etc.) + +#### Limitations +- ❌ **Not designed for CI/CD distributed testing** (per Playwright maintainers) +- ❌ **Intended for local development only** ("inner loop scenario") +- ❌ Cleared on new test runs (doesn't persist across clean environments) +- ❌ **GitHub Actions starts with clean environment** - `.last-run.json` not available on retry +- ❌ **Doesn't work with sharded tests** - each shard creates its own `.last-run.json` +- ❌ No native way to merge `.last-run.json` across shards +- ❌ Not designed for cross-workflow persistence + +#### CI/CD Workaround (Not Recommended) +To use `--last-failed` in GitHub Actions, you would need to: + +```yaml +- name: Run Playwright tests + id: playwright-test + run: npx playwright test + +- name: Upload last run state + if: failure() + uses: actions/upload-artifact@v4 + with: + name: last-run-state + path: test-results/.last-run.json + +# In retry workflow: +- name: Download last run state + uses: actions/download-artifact@v4 + with: + name: last-run-state + path: test-results/ + +- name: Rerun failed tests + run: npx playwright test --last-failed --update-snapshots +``` + +**Why This Isn't Ideal:** +- Playwright maintainers explicitly state this is not the intended use case +- Doesn't work well with sharded tests (multiple `.last-run.json` files) +- Requires manual artifact management +- More complex than the current custom approach for this use case + +### 2. File:Line Syntax for Specific Tests + +Playwright supports running tests at specific line numbers: + +```bash +# Run a specific test at line 42 +npx playwright test tests/example.spec.ts:42 + +# Multiple tests +npx playwright test tests/file1.spec.ts:10 tests/file2.spec.ts:25 + +# With snapshot updates +npx playwright test tests/example.spec.ts:42 --update-snapshots + +# With project selection +npx playwright test --project=chromium tests/example.spec.ts:42 +``` + +This is **exactly the format** the current custom manifest uses, making it compatible with Playwright's native CLI. + +### 3. Test Filtering Options + +```bash +# Filter by grep pattern +npx playwright test -g "screenshot" + +# Inverse grep +npx playwright test --grep-invert "mobile" + +# By project +npx playwright test --project=chromium + +# Multiple projects +npx playwright test --project=chromium --project=firefox + +# Specific directory +npx playwright test tests/screenshots/ +``` + +--- + +## Playwright Reporter Options + +### 1. JSON Reporter + +**Purpose:** Machine-readable test results + +#### Configuration +```typescript +// playwright.config.ts +export default defineConfig({ + reporter: [ + ['json', { outputFile: 'results.json' }] + ] +}) +``` + +Or via environment variable: +```bash +PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright test --reporter=json +``` + +#### Output Structure +```json +{ + "stats": { + "expected": 100, + "unexpected": 5, + "flaky": 2, + "skipped": 3 + }, + "suites": [ + { + "title": "Test Suite", + "specs": [ + { + "file": "browser_tests/example.test.ts", + "line": 42, + "tests": [ + { + "projectId": "chromium", + "results": [ + { + "status": "failed", + "attachments": [ + { "contentType": "image/png" } + ] + } + ] + } + ] + } + ] + } + ] +} +``` + +**This is the format** the current `build-failed-screenshot-manifest.ts` script parses. + +#### Advantages +- ✅ Stable, documented JSON schema (`@playwright/test/reporter`) +- ✅ Includes all test metadata (file, line, project, status, attachments) +- ✅ Can be used programmatically +- ✅ Supports multiple reporters simultaneously + +#### Current Project Usage +```yaml +# In tests-ci.yaml +PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ +pnpm exec playwright test --project=${{ matrix.browser }} \ + --reporter=list \ + --reporter=html \ + --reporter=json +``` + +### 2. Blob Reporter + +**Purpose:** Merging sharded test reports + +#### Configuration +```typescript +// playwright.config.ts +export default defineConfig({ + reporter: process.env.CI ? 'blob' : 'html' +}) +``` + +#### Usage with Sharding +```bash +# Run sharded test with blob output +npx playwright test --shard=1/4 --reporter=blob + +# Merge blob reports +npx playwright merge-reports --reporter=html ./all-blob-reports +npx playwright merge-reports --reporter=json ./all-blob-reports +``` + +#### Current Project Usage +```yaml +# Sharded chromium tests +- run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob + env: + PLAYWRIGHT_BLOB_OUTPUT_DIR: ../blob-report + +# Merge reports job +- run: | + pnpm exec playwright merge-reports --reporter=html ./all-blob-reports + PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ + pnpm exec playwright merge-reports --reporter=json ./all-blob-reports +``` + +#### Advantages +- ✅ Designed for distributed testing +- ✅ Can merge into any reporter format (HTML, JSON, etc.) +- ✅ Preserves all test information across shards + +#### Blob Reporter and `--last-failed` +- ❌ Blob reports **do not contain** a merged `.last-run.json` +- ❌ Each shard creates its own `.last-run.json` that isn't included in blob +- ❌ GitHub issue [#30924](https://github.com/microsoft/playwright/issues/30924) requests this feature (currently unsupported) + +### 3. Multiple Reporters + +You can use multiple reporters simultaneously: + +```typescript +export default defineConfig({ + reporter: [ + ['list'], // Terminal output + ['html'], // Browse results + ['json', { outputFile: 'results.json' }], // Programmatic parsing + ['junit', { outputFile: 'results.xml' }] // CI integration + ] +}) +``` + +Or via CLI: +```bash +npx playwright test --reporter=list --reporter=html --reporter=json +``` + +--- + +## GitHub Actions Integration Patterns + +### Pattern 1: Comment-Triggered Workflow (JupyterLab Approach) + +**Example:** [jupyterlab/jupyterlab-git](https://github.com/jupyterlab/jupyterlab-git/blob/main/.github/workflows/update-integration-tests.yml) + +```yaml +name: Update Playwright Snapshots + +on: + issue_comment: + types: [created, edited] + +permissions: + contents: write + pull-requests: write + +jobs: + update-snapshots: + # Only run for authorized users on PRs with specific comment + if: > + (github.event.issue.author_association == 'OWNER' || + github.event.issue.author_association == 'COLLABORATOR' || + github.event.issue.author_association == 'MEMBER' + ) && github.event.issue.pull_request && + contains(github.event.comment.body, 'please update snapshots') + runs-on: ubuntu-latest + + steps: + - name: React to the triggering comment + run: gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions --raw-field 'content=+1' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout PR branch + run: gh pr checkout ${{ github.event.issue.number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup and run tests + run: | + npm ci + npx playwright install --with-deps + npx playwright test --update-snapshots + + - name: Commit and push + run: | + git config user.name 'github-actions' + git config user.email 'github-actions@github.com' + git add . + git diff --cached --quiet || git commit -m "Update snapshots" + git push +``` + +#### Advantages +- ✅ Simple comment-based trigger +- ✅ Visual feedback (reaction on comment) +- ✅ Authorization checks built-in +- ✅ Auto-commits to PR branch + +#### Limitations +- ❌ Runs **all** tests with `--update-snapshots` (not selective) +- ❌ No integration with failed test information from CI + +### Pattern 2: Label-Based Trigger + Manifest (Current Approach) + +```yaml +name: Update Playwright Expectations + +on: + pull_request: + types: [labeled] + issue_comment: + types: [created] + +jobs: + test: + if: > + ( github.event_name == 'pull_request' && + github.event.label.name == 'New Browser Test Expectations' ) || + ( github.event.issue.pull_request && + startsWith(github.event.comment.body, '/update-playwright') ) + + steps: + # ... setup steps ... + + - name: Locate failed screenshot manifest artifact + id: locate-manifest + uses: actions/github-script@v8 + with: + script: | + const { owner, repo } = context.repo + let headSha = '' + if (context.eventName === 'pull_request') { + headSha = context.payload.pull_request.head.sha + } else if (context.eventName === 'issue_comment') { + const prNumber = context.payload.issue.number + const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber }) + headSha = pr.data.head.sha + } + + const { data } = await github.rest.actions.listWorkflowRuns({ + owner, repo, + workflow_id: 'tests-ci.yaml', + head_sha: headSha, + per_page: 1, + }) + const run = data.workflow_runs?.[0] + + let has = 'false' + if (run) { + const { data: { artifacts = [] } } = await github.rest.actions.listWorkflowRunArtifacts({ + owner, repo, run_id: run.id + }) + if (artifacts.some(a => a.name === 'failed-screenshot-tests' && !a.expired)) + has = 'true' + } + core.setOutput('has_manifest', has) + + - name: Download failed screenshot manifest + if: steps.locate-manifest.outputs.has_manifest == 'true' + uses: actions/download-artifact@v4 + with: + run-id: ${{ steps.locate-manifest.outputs.run_id }} + name: failed-screenshot-tests + path: ComfyUI_frontend/ci-rerun + + - name: Re-run failed screenshot tests + run: | + if [ ! -d ci-rerun ]; then + echo "No manifest found; running full suite" + pnpm exec playwright test --update-snapshots + exit 0 + fi + + for f in ci-rerun/*.txt; do + project="$(basename "$f" .txt)" + mapfile -t lines < "$f" + filtered=() + for l in "${lines[@]}"; do + [ -n "$l" ] && filtered+=("$l") + done + + if [ ${#filtered[@]} -gt 0 ]; then + echo "Re-running ${#filtered[@]} tests for project $project" + pnpm exec playwright test --project="$project" --update-snapshots "${filtered[@]}" + fi + done +``` + +#### Advantages +- ✅ **Selective** - only re-runs failed screenshot tests +- ✅ Works across different trigger mechanisms (label or comment) +- ✅ Fallback to full suite if manifest not found +- ✅ Per-project manifests support multiple browser configurations +- ✅ Handles sharded tests via merged report + +### Pattern 3: WordPress/Openverse Approach (Always Update) + +Proposed pattern (not fully implemented): +1. CI always runs with `--update-snapshots` flag +2. If snapshots change, create/update a secondary branch +3. Open PR targeting the original PR branch +4. Developer reviews snapshot changes before merging + +#### Advantages +- ✅ Always generates correct snapshots +- ✅ Snapshot changes are visible in separate PR +- ✅ No test failures due to mismatched snapshots + +#### Limitations +- ❌ Creates multiple PRs +- ❌ More complex merge workflow +- ❌ Potential for snapshot changes to mask real issues + +### Pattern 4: Manual Workflow Dispatch + +```yaml +name: Update Snapshots + +on: + workflow_dispatch: + inputs: + update-snapshots: + description: 'Update snapshots' + type: boolean + default: false + test-pattern: + description: 'Test pattern (optional)' + type: string + required: false + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup + run: | + npm ci + npx playwright install --with-deps + + - name: Run tests + run: | + if [ "${{ inputs.update-snapshots }}" = "true" ]; then + FLAGS="--update-snapshots" + fi + + PATTERN="${{ inputs.test-pattern }}" + npx playwright test ${PATTERN} ${FLAGS} +``` + +#### Advantages +- ✅ Full manual control +- ✅ Can specify test patterns +- ✅ Simple to understand + +#### Limitations +- ❌ Requires manual triggering +- ❌ Not integrated with CI failures + +--- + +## Third-Party Solutions + +### Currents.dev - Last Failed GitHub Action + +**Repository:** [currents-dev/playwright-last-failed](https://github.com/currents-dev/playwright-last-failed) + +#### Purpose +Helps run last failed Playwright tests using Currents' cloud-based caching service. + +#### Usage +```yaml +- name: Playwright Last Failed action + id: last-failed-action + uses: currents-dev/playwright-last-failed@v1 + with: + pw-output-dir: test-results + matrix-index: ${{ matrix.shard }} + matrix-total: ${{ strategy.job-total }} +``` + +#### How It Works +- Uses Currents' cloud service to persist failed test information +- Supports sharded tests via matrix parameters +- Enables selective rerun of failed tests across workflow retries + +#### Advantages +- ✅ Works with sharded tests +- ✅ Persists across workflow runs +- ✅ Supports GitHub Actions retry mechanism +- ✅ Handles distributed testing + +#### Limitations +- ❌ **Requires Currents subscription** (third-party paid service) +- ❌ Dependency on external service +- ❌ Data sent to third-party cloud +- ❌ Additional cost +- ❌ Vendor lock-in + +#### Recommendation +**Not suitable for this project** due to: +- External service dependency +- Cost implications +- The current custom solution is already working well + +--- + +## Comparison and Recommendations + +### Feature Matrix + +| Feature | Current Approach | `--last-failed` | Currents | Comment Trigger Only | +|---------|-----------------|-----------------|----------|---------------------| +| Works with sharded tests | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes | +| Persists across workflows | ✅ Yes | ❌ No | ✅ Yes | N/A | +| Selective reruns | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No (runs all) | +| No external dependencies | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes | +| Simple implementation | ⚠️ Medium | ✅ Simple | ✅ Simple | ✅ Simple | +| Maintenance overhead | ⚠️ Medium | ✅ Low | ✅ Low | ✅ Low | +| Works in CI/CD | ✅ Yes | ⚠️ Workaround | ✅ Yes | ✅ Yes | +| Cost | ✅ Free | ✅ Free | ❌ Paid | ✅ Free | +| Supports multiple projects | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | + +### Why `--last-failed` Isn't Suitable (Currently) + +1. **Not designed for CI/CD:** Playwright maintainers explicitly state it's for "inner loop scenario (local development)" +2. **Doesn't work with sharded tests:** Each shard creates its own `.last-run.json` with no native merge +3. **Clean environment issue:** GitHub Actions starts fresh, losing `.last-run.json` +4. **Feature request pending:** GitHub issue [#30924](https://github.com/microsoft/playwright/issues/30924) requests blob report integration (not yet implemented) + +### Recommendations + +#### Short Term: Keep Current Approach +**Verdict: The current custom manifest approach is the best solution for this project's needs.** + +**Reasons:** +1. ✅ **Works perfectly with sharded tests** - merges results across 8 shards +2. ✅ **Persists across workflows** - artifact storage for 7 days +3. ✅ **Selective reruns** - only failed screenshot tests +4. ✅ **No external dependencies** - fully self-contained +5. ✅ **Uses stable Playwright JSON format** - typed via `@playwright/test/reporter` +6. ✅ **Already working well** - proven in production + +**Minor Improvements:** +```typescript +// Add version check to warn if JSON schema changes +import { version } from '@playwright/test/package.json' +if (major(version) !== 1) { + console.warn('Playwright major version changed - verify JSON schema compatibility') +} + +// Add more robust error handling +try { + const report: JSONReport = JSON.parse(raw) +} catch (error) { + throw new Error(`Failed to parse Playwright JSON report: ${error.message}`) +} + +// Consider adding tests for the manifest builder +// e.g., tests/cicd/build-failed-screenshot-manifest.test.ts +``` + +#### Long Term: Monitor Playwright Development + +**Watch for these features:** +1. **Blob report + `.last-run.json` merge** - GitHub issue [#30924](https://github.com/microsoft/playwright/issues/30924) +2. **Native CI/CD support for `--last-failed`** - may never happen (by design) +3. **Report merging improvements** - GitHub issue [#33094](https://github.com/microsoft/playwright/issues/33094) + +**Migration path if native support improves:** +```yaml +# Future potential approach (if Playwright adds this feature) +- name: Merge reports with last-run + run: | + npx playwright merge-reports --reporter=html ./all-blob-reports + npx playwright merge-reports --reporter=last-failed ./all-blob-reports + +- name: Upload merged last-run + uses: actions/upload-artifact@v4 + with: + name: last-run-state + path: test-results/.last-run.json + +# In update workflow +- name: Download last-run state + uses: actions/download-artifact@v4 + with: + name: last-run-state + path: test-results/ + +- name: Update snapshots for failed tests + run: npx playwright test --last-failed --update-snapshots +``` + +**However, this is speculative** - Playwright maintainers have indicated `--last-failed` is not intended for CI/CD. + +#### Alternative: Simplify to Full Suite Reruns + +If the custom manifest becomes too complex to maintain, consider: + +```yaml +- name: Re-run ALL screenshot tests + run: | + # Simple grep-based filtering for screenshot tests + npx playwright test -g "screenshot" --update-snapshots +``` + +**Trade-offs:** +- ✅ Much simpler +- ✅ No custom scripts +- ❌ Slower (runs all screenshot tests, not just failed ones) +- ❌ Potentially updates snapshots that weren't actually failing + +--- + +## Conclusion + +The current custom manifest approach is **well-designed** and **appropriate** for this project's requirements: + +1. **Handles sharded tests** - critical for CI performance +2. **Selective reruns** - saves time and resources +3. **Stable implementation** - uses documented Playwright JSON schema +4. **No external dependencies** - fully controlled + +While `--last-failed` is a nice feature for **local development**, Playwright's own documentation and maintainer comments confirm it's **not suitable for distributed CI/CD testing**, which is exactly what this project needs. + +The only potentially better solution (Currents) requires a paid external service, which adds cost and complexity without significant benefits over the current approach. + +**Recommendation: Keep the current implementation**, with minor improvements to error handling and documentation. Monitor Playwright development for native improvements, but don't expect `--last-failed` to become a viable alternative for this use case. + +--- + +## References + +### Official Playwright Documentation +- [Command Line](https://playwright.dev/docs/test-cli) +- [Reporters](https://playwright.dev/docs/test-reporters) +- [Test Sharding](https://playwright.dev/docs/test-sharding) +- [CI/CD Setup](https://playwright.dev/docs/ci-intro) + +### Community Resources +- [Playwright Solutions: How to Run Failures Only](https://playwrightsolutions.com/how-to-run-failures-only-from-the-last-playwright-run/) +- [Medium: How to Run Only Last Failed Tests](https://medium.com/@testerstalk/how-to-run-only-last-failed-tests-in-playwright-e5e41472594a) +- [Medium: Streamlining Visual Regression Testing](https://medium.com/@haleywardo/streamlining-playwright-visual-regression-testing-with-github-actions-e077fd33c27c) + +### GitHub Issues +- [#30924 - Last-failed with blob reports](https://github.com/microsoft/playwright/issues/30924) +- [#33094 - Merging main run with --last-failed](https://github.com/microsoft/playwright/issues/33094) +- [#28254 - Feature request for --last-failed](https://github.com/microsoft/playwright/issues/28254) + +### Example Implementations +- [JupyterLab Git - Update Integration Tests](https://github.com/jupyterlab/jupyterlab-git/blob/main/.github/workflows/update-integration-tests.yml) +- [WordPress Openverse - Discussion #4535](https://github.com/WordPress/openverse/issues/4535) + +### Third-Party Tools +- [Currents - Playwright Last Failed Action](https://github.com/currents-dev/playwright-last-failed) +- [Currents - Re-run Only Failed Tests](https://docs.currents.dev/guides/re-run-only-failed-tests) diff --git a/docs/SNAPSHOT_UPDATE_FROM_ACTUALS.md b/docs/SNAPSHOT_UPDATE_FROM_ACTUALS.md new file mode 100644 index 0000000000..603f5b6cc3 --- /dev/null +++ b/docs/SNAPSHOT_UPDATE_FROM_ACTUALS.md @@ -0,0 +1,482 @@ +# Snapshot Update from Actual Files (Fast Approach) + +**Date:** 2025-10-08 +**Status:** Proposed Optimization + +## Overview + +When Playwright snapshot tests fail, Playwright **already generates the new ("actual") snapshots**. Instead of re-running tests with `--update-snapshots`, we can extract these actual snapshots from the `test-results/` directory and copy them to overwrite the expected snapshots. + +**Performance improvement:** ~1-2 minutes → **~10-30 seconds** + +## How Playwright Stores Snapshots + +### Expected (Baseline) Snapshots + +Stored in: `-snapshots/--.png` + +**Example:** +``` +browser_tests/tests/interaction.spec.ts-snapshots/default-chromium-linux.png +``` + +### Failed Test Artifacts + +When a snapshot test fails, Playwright creates: + +``` +test-results// + ├── -actual.png # The NEW screenshot + ├── -expected.png # Copy of baseline + └── -diff.png # Visual diff +``` + +**Example:** +``` +test-results/interaction-default-chromium-67af3c/ + ├── default-1-actual.png + ├── default-1-expected.png + └── default-1-diff.png +``` + +## Current Approach vs. Proposed Approach + +### Current: Re-run Tests with `--update-snapshots` + +```yaml +# Current workflow (.github/workflows/update-playwright-expectations.yaml) +- name: Re-run failed screenshot tests and update snapshots + run: | + # Download manifest of failed tests + # For each project: chromium, chromium-2x, etc. + # Run: playwright test --project="$project" --update-snapshots test1.spec.ts:42 test2.spec.ts:87 ... +``` + +**Time:** ~2-5 minutes (depends on # of failed tests) + +**Why slow:** +- Re-executes tests (browser startup, navigation, interactions) +- Waits for elements, animations, etc. +- Generates HTML report +- Each test takes 5-15 seconds + +### Proposed: Copy Actual → Expected + +```yaml +# Proposed workflow +- name: Download test artifacts (includes test-results/) +- name: Copy actual snapshots to expected locations + run: pnpm tsx scripts/cicd/update-snapshots-from-actuals.ts +- name: Commit and push +``` + +**Time:** ~10-30 seconds (just file operations) + +**Why fast:** +- No test execution +- No browser startup +- Just file copying +- Parallel file operations + +## Implementation Plan + +### Step 1: Modify tests-ci.yaml + +Currently, test artifacts upload only the `playwright-report/` directory. + +**Add test-results/ to artifacts:** + +```yaml +# .github/workflows/tests-ci.yaml +- uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-results-${{ matrix.browser }} # New artifact + path: | + ComfyUI_frontend/test-results/**/*-actual.png + ComfyUI_frontend/test-results/**/*-expected.png + ComfyUI_frontend/test-results/**/*-diff.png + retention-days: 7 +``` + +**Optimization:** Only upload actual snapshots for failed tests (saves artifact storage) + +### Step 2: Create Script to Map Actuals → Expected + +**File:** `scripts/cicd/update-snapshots-from-actuals.ts` + +```typescript +import type { JSONReport, JSONReportTestResult } from '@playwright/test/reporter' +import fs from 'node:fs' +import fsp from 'node:fs/promises' +import path from 'node:path' + +interface SnapshotMapping { + actualPath: string // test-results/.../snapshot-1-actual.png + expectedPath: string // browser_tests/tests/foo.spec.ts-snapshots/snapshot-chromium-linux.png + testFile: string + testName: string + project: string +} + +async function main() { + const reportPath = path.join('playwright-report', 'report.json') + + if (!fs.existsSync(reportPath)) { + console.log('No report.json found - no failed tests to update') + return + } + + const raw = await fsp.readFile(reportPath, 'utf8') + const report: JSONReport = JSON.parse(raw) + + const mappings: SnapshotMapping[] = [] + + // Parse JSON report to extract snapshot paths + function collectFailedSnapshots(suite: any) { + if (!suite) return + + for (const childSuite of suite.suites ?? []) { + collectFailedSnapshots(childSuite) + } + + for (const spec of suite.specs ?? []) { + for (const test of spec.tests) { + const lastResult = test.results[test.results.length - 1] + + if (lastResult?.status !== 'failed') continue + + // Check if test has image attachments (indicates screenshot test) + const imageAttachments = lastResult.attachments.filter( + (att: any) => att?.contentType?.startsWith('image/') + ) + + if (imageAttachments.length === 0) continue + + // Extract snapshot mapping from attachments + for (const attachment of imageAttachments) { + const attachmentPath = attachment.path + + if (!attachmentPath || !attachmentPath.includes('-actual.png')) { + continue + } + + // Parse test-results path to determine expected location + // test-results/interaction-default-chromium-67af3c/default-1-actual.png + // → browser_tests/tests/interaction.spec.ts-snapshots/default-chromium-linux.png + + const actualPath = attachmentPath + const expectedPath = inferExpectedPath(actualPath, spec.file, test.projectId) + + if (expectedPath) { + mappings.push({ + actualPath, + expectedPath, + testFile: spec.file, + testName: test.annotations[0]?.description || test.title, + project: test.projectId + }) + } + } + } + } + } + + collectFailedSnapshots(report) + + if (mappings.length === 0) { + console.log('No failed snapshot tests found') + return + } + + console.log(`Found ${mappings.length} snapshots to update`) + + // Copy actual → expected + let successCount = 0 + let errorCount = 0 + + for (const mapping of mappings) { + try { + if (!fs.existsSync(mapping.actualPath)) { + console.warn(`⚠️ Actual file not found: ${mapping.actualPath}`) + errorCount++ + continue + } + + // Ensure expected directory exists + const expectedDir = path.dirname(mapping.expectedPath) + await fsp.mkdir(expectedDir, { recursive: true }) + + // Copy actual → expected + await fsp.copyFile(mapping.actualPath, mapping.expectedPath) + + console.log(`✓ Updated: ${path.basename(mapping.expectedPath)}`) + successCount++ + } catch (error) { + console.error(`✗ Failed to update ${mapping.expectedPath}:`, error) + errorCount++ + } + } + + console.log(`\n✅ Successfully updated ${successCount} snapshots`) + if (errorCount > 0) { + console.log(`⚠️ Failed to update ${errorCount} snapshots`) + process.exit(1) + } +} + +/** + * Infer the expected snapshot path from the actual path + * + * Actual: test-results/interaction-default-chromium-67af3c/default-1-actual.png + * Expected: browser_tests/tests/interaction.spec.ts-snapshots/default-chromium-linux.png + */ +function inferExpectedPath(actualPath: string, testFile: string, projectId: string): string | null { + try { + // Extract snapshot name from actual path + // "default-1-actual.png" → "default" + const actualFilename = path.basename(actualPath) + const snapshotName = actualFilename.replace(/-\d+-actual\.png$/, '') + + // Determine platform (linux, darwin, win32) + const platform = process.platform === 'linux' ? 'linux' + : process.platform === 'darwin' ? 'darwin' + : 'win32' + + // Build expected path + const testDir = path.dirname(testFile) + const testBasename = path.basename(testFile) + const snapshotsDir = path.join(testDir, `${testBasename}-snapshots`) + const expectedFilename = `${snapshotName}-${projectId}-${platform}.png` + + return path.join(snapshotsDir, expectedFilename) + } catch (error) { + console.error(`Failed to infer expected path for ${actualPath}:`, error) + return null + } +} + +main().catch((err) => { + console.error('Failed to update snapshots:', err) + process.exit(1) +}) +``` + +### Step 3: Better Approach - Use Playwright's Attachment Metadata + +The JSON reporter actually includes the **expected snapshot path** in the attachments! + +**Simplified script:** + +```typescript +async function main() { + const report: JSONReport = JSON.parse(await fsp.readFile('playwright-report/report.json', 'utf8')) + + const updates: Array<{ actual: string; expected: string }> = [] + + for (const result of getAllTestResults(report)) { + if (result.status !== 'failed') continue + + for (const attachment of result.attachments) { + // Playwright includes both actual and expected in attachments + if (attachment.name?.includes('-actual') && attachment.path) { + const actualPath = attachment.path + + // Find corresponding expected attachment + const expectedAttachment = result.attachments.find( + att => att.name === attachment.name.replace('-actual', '-expected') + ) + + if (expectedAttachment?.path) { + // The expected path in attachment points to the test-results copy + // But we can infer the real expected path from the attachment metadata + const expectedPath = inferRealExpectedPath(expectedAttachment) + updates.push({ actual: actualPath, expected: expectedPath }) + } + } + } + } + + // Copy files + for (const { actual, expected } of updates) { + await fsp.copyFile(actual, expected) + console.log(`✓ Updated: ${path.relative(process.cwd(), expected)}`) + } +} +``` + +### Step 4: Update GitHub Actions Workflow + +```yaml +# .github/workflows/update-playwright-expectations.yaml +name: Update Playwright Expectations + +on: + issue_comment: + types: [created] + +jobs: + update: + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '/update-snapshots') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), + github.event.comment.author_association) + runs-on: ubuntu-latest + steps: + - name: React to comment + uses: actions/github-script@v8 + with: + script: | + github.rest.reactions.createForIssueComment({ + comment_id: context.payload.comment.id, + content: '+1' + }) + + - name: Checkout PR + run: gh pr checkout ${{ github.event.issue.number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Frontend + uses: ./.github/actions/setup-frontend + + - name: Get latest failed test run + id: get-run + uses: actions/github-script@v8 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number + }) + + const runs = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'tests-ci.yaml', + head_sha: pr.data.head.sha, + per_page: 1 + }) + + core.setOutput('run_id', runs.data.workflow_runs[0]?.id || '') + + - name: Download test results + uses: actions/download-artifact@v4 + with: + run-id: ${{ steps.get-run.outputs.run_id }} + pattern: playwright-results-* + path: ComfyUI_frontend/test-results + merge-multiple: true + + - name: Download JSON report + uses: actions/download-artifact@v4 + with: + run-id: ${{ steps.get-run.outputs.run_id }} + pattern: playwright-report-* + path: ComfyUI_frontend/playwright-report + merge-multiple: true + + - name: Update snapshots from actuals + working-directory: ComfyUI_frontend + run: pnpm tsx scripts/cicd/update-snapshots-from-actuals.ts + + - name: Commit and push + working-directory: ComfyUI_frontend + run: | + git config user.name 'github-actions' + git config user.email 'github-actions@github.com' + git add browser_tests/**/*-snapshots/*.png + + if git diff --cached --quiet; then + echo "No snapshot changes detected" + else + git commit -m "[automated] Update test expectations" + git push + fi +``` + +## Performance Comparison + +### Current Approach: Re-run Tests + +| Step | Time | +|------|------| +| Download manifest | 5s | +| Install Playwright browsers | 20s | +| Re-run 50 failed tests | 2-3 min | +| Generate report | 10s | +| Commit and push | 10s | +| **Total** | **~3-4 min** | + +### Proposed Approach: Copy Actuals + +| Step | Time | +|------|------| +| Download test-results artifacts | 10s | +| Download JSON report | 2s | +| Run copy script | 5s | +| Commit and push | 10s | +| **Total** | **~30s** | + +**Speedup: 6-8x faster** ⚡ + +## Advantages + +✅ **Much faster** - No test re-execution +✅ **Simpler** - No need for manifest generation +✅ **Fewer dependencies** - No Playwright browser install needed +✅ **Less resource usage** - No ComfyUI server, no browser processes +✅ **More reliable** - File operations are deterministic +✅ **Already tested** - The snapshots were generated during the actual test run + +## Disadvantages / Edge Cases + +❌ **New snapshots** - If a test creates a snapshot for the first time, there's no existing expected file. This is rare and can be handled by fallback to re-running. + +❌ **Deleted tests** - Old snapshots won't be cleaned up automatically. Could add a cleanup step. + +❌ **Multiple projects** - Each project (chromium, chromium-2x, mobile-chrome) generates separate actuals. The script needs to handle all of them. + +❌ **Artifact storage** - Storing test-results/ increases artifact size. Mitigation: Only upload `-actual.png` files, not traces/videos. + +## Hybrid Approach (Recommended) + +Use the fast copy approach **with fallback**: + +```yaml +- name: Update snapshots + run: | + # Try fast approach first + if pnpm tsx scripts/cicd/update-snapshots-from-actuals.ts; then + echo "✓ Updated snapshots from actuals" + else + echo "⚠ Fast update failed, falling back to re-running tests" + # Fallback to current approach + pnpm exec playwright test --update-snapshots --project=chromium ... + fi +``` + +## Implementation Checklist + +- [ ] Create `scripts/cicd/update-snapshots-from-actuals.ts` +- [ ] Update `tests-ci.yaml` to upload `test-results/` artifacts +- [ ] Update `update-playwright-expectations.yaml` to use new script +- [ ] Add fallback logic for edge cases +- [ ] Test with actual PR +- [ ] Update documentation +- [ ] Consider switching from label trigger → comment trigger (`/update-snapshots`) + +## Related Links + +- **Playwright snapshot docs:** https://playwright.dev/docs/test-snapshots +- **JSON reporter types:** `@playwright/test/reporter` +- **GitHub Actions artifacts:** https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts +- **Issue #22064:** Playwright feature request for better snapshot file alignment + +## Conclusion + +This approach is **significantly faster** and **simpler** than re-running tests. The main trade-off is artifact storage size, but this can be mitigated by only uploading actual snapshots (not traces/videos). + +**Recommendation:** Implement this as the primary approach with fallback to re-running tests for edge cases. diff --git a/eslint.config.ts b/eslint.config.ts index 96d68055c3..c64ae14aba 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,77 +1,104 @@ // For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format import pluginJs from '@eslint/js' import pluginI18n from '@intlify/eslint-plugin-vue-i18n' +import { importX } from 'eslint-plugin-import-x' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import storybook from 'eslint-plugin-storybook' +import tailwind from 'eslint-plugin-tailwindcss' import unusedImports from 'eslint-plugin-unused-imports' import pluginVue from 'eslint-plugin-vue' import { defineConfig } from 'eslint/config' import globals from 'globals' -import tseslint from 'typescript-eslint' +import { + configs as tseslintConfigs, + parser as tseslintParser +} from 'typescript-eslint' import vueParser from 'vue-eslint-parser' const extraFileExtensions = ['.vue'] +const commonGlobals = { + ...globals.browser, + __COMFYUI_FRONTEND_VERSION__: 'readonly' +} as const + +const settings = { + 'import/resolver': { + typescript: true, + node: true + }, + tailwindcss: { + config: `${import.meta.dirname}/packages/design-system/src/css/style.css`, + functions: ['cn', 'clsx', 'tw'] + } +} as const + +const commonParserOptions = { + parser: tseslintParser, + projectService: true, + tsConfigRootDir: import.meta.dirname, + ecmaVersion: 2020, + sourceType: 'module', + extraFileExtensions +} as const + export default defineConfig([ { ignores: [ - 'src/scripts/*', - 'src/extensions/core/*', - 'src/types/vue-shim.d.ts', - 'packages/registry-types/src/comfyRegistryTypes.ts', - 'src/types/generatedManagerTypes.ts', + '.i18nrc.cjs', + 'components.d.ts', + 'lint-staged.config.js', + 'vitest.setup.ts', '**/vite.config.*.timestamp*', - '**/vitest.config.*.timestamp*' + '**/vitest.config.*.timestamp*', + 'packages/registry-types/src/comfyRegistryTypes.ts', + 'src/extensions/core/*', + 'src/scripts/*', + 'src/types/generatedManagerTypes.ts', + 'src/types/vue-shim.d.ts' ] }, { files: ['./**/*.{ts,mts}'], + settings, languageOptions: { - globals: { - ...globals.browser, - __COMFYUI_FRONTEND_VERSION__: 'readonly' - }, + globals: commonGlobals, parserOptions: { - parser: tseslint.parser, + ...commonParserOptions, projectService: { allowDefaultProject: [ - 'vite.config.mts', 'vite.electron.config.mts', 'vite.types.config.mts', 'playwright.config.ts', 'playwright.i18n.config.ts' ] - }, - tsConfigRootDir: import.meta.dirname, - ecmaVersion: 2020, - sourceType: 'module', - extraFileExtensions + } } } }, { files: ['./**/*.vue'], + settings, languageOptions: { - globals: { - ...globals.browser, - __COMFYUI_FRONTEND_VERSION__: 'readonly' - }, + globals: commonGlobals, parser: vueParser, - parserOptions: { - parser: tseslint.parser, - projectService: true, - tsConfigRootDir: import.meta.dirname, - ecmaVersion: 2020, - sourceType: 'module', - extraFileExtensions - } + parserOptions: commonParserOptions } }, pluginJs.configs.recommended, - tseslint.configs.recommended, + + tseslintConfigs.recommended, + // Difference in typecheck on CI vs Local + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Bad types in the plugin + tailwind.configs['flat/recommended'], pluginVue.configs['flat/recommended'], eslintPluginPrettierRecommended, storybook.configs['flat/recommended'], + // @ts-expect-error Bad types in the plugin + importX.flatConfigs.recommended, + // @ts-expect-error Bad types in the plugin + importX.flatConfigs.typescript, { plugins: { 'unused-imports': unusedImports, @@ -91,13 +118,18 @@ export default defineConfig([ allowInterfaces: 'always' } ], + 'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import-x/no-useless-path-segments': 'error', + 'import-x/no-relative-packages': 'error', 'unused-imports/no-unused-imports': 'error', 'no-console': ['error', { allow: ['warn', 'error'] }], + 'tailwindcss/no-custom-classname': 'off', // TODO: fix 'vue/no-v-html': 'off', // Enforce dark-theme: instead of dark: prefix 'vue/no-restricted-class': ['error', '/^dark:/'], 'vue/multi-word-component-names': 'off', // TODO: fix 'vue/no-template-shadow': 'off', // TODO: fix + 'vue/match-component-import-name': 'error', /* Toggle on to do additional until we can clean up existing violations. 'vue/no-unused-emit-declarations': 'error', 'vue/no-unused-properties': 'error', diff --git a/package.json b/package.json index 171caf9f75..9076140c5f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.28.6", + "version": "1.29.1", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", @@ -11,7 +11,7 @@ "build:desktop": "nx build @comfyorg/desktop-ui", "build-storybook": "storybook build", "build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js", - "build": "pnpm typecheck && nx build", + "build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' pnpm typecheck && nx build", "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", "dev:desktop": "nx dev @comfyorg/desktop-ui", "dev:electron": "nx serve --config vite.electron.config.mts", @@ -35,8 +35,8 @@ "prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true", "preview": "nx preview", "storybook": "nx storybook -p 6006", - "stylelint:fix": "stylelint --cache --fix", - "stylelint": "stylelint --cache", + "stylelint:fix": "stylelint --cache --fix '{apps,packages,src}/**/*.{css,vue}'", + "stylelint": "stylelint --cache '{apps,packages,src}/**/*.{css,vue}'", "test:browser": "pnpm exec nx e2e", "test:unit": "nx run test", "typecheck": "vue-tsc --noEmit", @@ -57,6 +57,7 @@ "@storybook/vue3-vite": "catalog:", "@tailwindcss/vite": "catalog:", "@trivago/prettier-plugin-sort-imports": "catalog:", + "@types/eslint-plugin-tailwindcss": "catalog:", "@types/fs-extra": "catalog:", "@types/jsdom": "catalog:", "@types/node": "catalog:", @@ -66,10 +67,14 @@ "@vitest/coverage-v8": "catalog:", "@vitest/ui": "catalog:", "@vue/test-utils": "catalog:", + "cross-env": "catalog:", "eslint": "catalog:", "eslint-config-prettier": "catalog:", + "eslint-import-resolver-typescript": "catalog:", + "eslint-plugin-import-x": "catalog:", "eslint-plugin-prettier": "catalog:", "eslint-plugin-storybook": "catalog:", + "eslint-plugin-tailwindcss": "catalog:", "eslint-plugin-unused-imports": "catalog:", "eslint-plugin-vue": "catalog:", "fs-extra": "^11.2.0", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index af18f4e3ed..5208d4bbb6 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -129,6 +129,7 @@ /* --- */ + --accent-primary: var(--color-charcoal-700); --backdrop: var(--color-white); --dialog-surface: var(--color-neutral-200); --node-component-border: var(--color-gray-400); @@ -154,13 +155,20 @@ from var(--color-zinc-500) r g b / 10% ); --node-component-widget-skeleton-surface: var(--color-zinc-300); - --node-stroke: var(--color-stone-100); + --node-stroke: var(--color-gray-400); + --node-stroke-selected: var(--color-accent-primary); + --node-stroke-error: var(--color-error); + --node-stroke-executing: var(--color-blue-100); } .dark-theme { + --accent-primary: var(--color-pure-white); --backdrop: var(--color-neutral-900); --dialog-surface: var(--color-neutral-700); --node-component-border: var(--color-stone-200); + --node-component-border-error: var(--color-danger-100); + --node-component-border-executing: var(--color-blue-500); + --node-component-border-selected: var(--color-charcoal-200); --node-component-header-icon: var(--color-slate-300); --node-component-header-surface: var(--color-charcoal-800); --node-component-outline: var(--color-white); @@ -176,7 +184,10 @@ --node-component-tooltip-border: var(--color-slate-300); --node-component-tooltip-surface: var(--color-charcoal-800); --node-component-widget-skeleton-surface: var(--color-zinc-800); - --node-stroke: var(--color-slate-100); + --node-stroke: var(--color-stone-200); + --node-stroke-selected: var(--color-pure-white); + --node-stroke-error: var(--color-error); + --node-stroke-executing: var(--color-blue-100); } @theme inline { @@ -214,6 +225,9 @@ --node-component-widget-skeleton-surface ); --color-node-stroke: var(--node-stroke); + --color-node-stroke-selected: var(--node-stroke-selected); + --color-node-stroke-error: var(--node-stroke-error); + --color-node-stroke-executing: var(--node-stroke-executing); } @custom-variant dark-theme { @@ -645,13 +659,10 @@ button.comfy-close-menu-btn { } span.drag-handle { - width: 10px; - height: 20px; display: inline-block; overflow: hidden; line-height: 5px; padding: 3px 4px; - cursor: move; vertical-align: middle; margin-top: -0.4em; margin-left: -0.2em; @@ -1049,6 +1060,11 @@ audio.comfy-audio.empty-audio-widget { transition: none; } +.isLOD .lg-node-header { + border-radius: 0px; + pointer-events: none; +} + .isLOD .lg-node-widgets { pointer-events: none; } diff --git a/packages/design-system/src/icons/play.svg b/packages/design-system/src/icons/play.svg new file mode 100644 index 0000000000..19c7090831 --- /dev/null +++ b/packages/design-system/src/icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/shared-frontend-utils/src/formatUtil.ts b/packages/shared-frontend-utils/src/formatUtil.ts index a5d525c82d..658498f1fd 100644 --- a/packages/shared-frontend-utils/src/formatUtil.ts +++ b/packages/shared-frontend-utils/src/formatUtil.ts @@ -82,7 +82,7 @@ export function formatSize(value?: number) { * - filename: 'file' * - suffix: 'txt' */ -function getFilenameDetails(fullFilename: string) { +export function getFilenameDetails(fullFilename: string) { if (fullFilename.includes('.')) { return { filename: fullFilename.split('.').slice(0, -1).join('.'), @@ -451,3 +451,26 @@ export function stringToLocale(locale: string): SupportedLocale { ? (locale as SupportedLocale) : 'en' } + +export function formatDuration(milliseconds: number): string { + if (!milliseconds || milliseconds < 0) return '0s' + + const totalSeconds = Math.floor(milliseconds / 1000) + const hours = Math.floor(totalSeconds / 3600) + const minutes = Math.floor((totalSeconds % 3600) / 60) + const remainingSeconds = Math.floor(totalSeconds % 60) + + const parts: string[] = [] + + if (hours > 0) { + parts.push(`${hours}h`) + } + if (minutes > 0) { + parts.push(`${minutes}m`) + } + if (remainingSeconds > 0 || parts.length === 0) { + parts.push(`${remainingSeconds}s`) + } + + return parts.join(' ') +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f0094338b..3b362bc15b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,9 @@ catalogs: '@trivago/prettier-plugin-sort-imports': specifier: ^5.2.0 version: 5.2.2 + '@types/eslint-plugin-tailwindcss': + specifier: ^3.17.0 + version: 3.17.0 '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 @@ -123,6 +126,9 @@ catalogs: axios: specifier: ^1.8.2 version: 1.11.0 + cross-env: + specifier: ^10.1.0 + version: 10.1.0 dotenv: specifier: ^16.4.5 version: 16.6.1 @@ -132,12 +138,21 @@ catalogs: eslint-config-prettier: specifier: ^10.1.8 version: 10.1.8 + eslint-import-resolver-typescript: + specifier: ^4.4.4 + version: 4.4.4 + eslint-plugin-import-x: + specifier: ^4.16.1 + version: 4.16.1 eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4 eslint-plugin-storybook: specifier: ^9.1.6 version: 9.1.6 + eslint-plugin-tailwindcss: + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0 eslint-plugin-unused-imports: specifier: ^4.2.0 version: 4.2.0 @@ -265,6 +280,9 @@ catalogs: specifier: ^3.3.0 version: 3.3.0 +overrides: + '@types/eslint': '-' + importers: .: @@ -480,6 +498,9 @@ importers: '@trivago/prettier-plugin-sort-imports': specifier: 'catalog:' version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2) + '@types/eslint-plugin-tailwindcss': + specifier: 'catalog:' + version: 3.17.0 '@types/fs-extra': specifier: 'catalog:' version: 11.0.4 @@ -507,18 +528,30 @@ importers: '@vue/test-utils': specifier: 'catalog:' version: 2.4.6 + cross-env: + specifier: 'catalog:' + version: 10.1.0 eslint: specifier: 'catalog:' version: 9.35.0(jiti@2.4.2) eslint-config-prettier: specifier: 'catalog:' version: 10.1.8(eslint@9.35.0(jiti@2.4.2)) + eslint-import-resolver-typescript: + specifier: 'catalog:' + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.4.2)) + eslint-plugin-import-x: + specifier: 'catalog:' + version: 4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-prettier: specifier: 'catalog:' version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2) eslint-plugin-storybook: specifier: 'catalog:' version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + eslint-plugin-tailwindcss: + specifier: 'catalog:' + version: 4.0.0-beta.0(tailwindcss@4.1.12) eslint-plugin-unused-imports: specifier: 'catalog:' version: 4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)) @@ -1425,6 +1458,9 @@ packages: '@emnapi/wasi-threads@1.0.4': resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -2141,6 +2177,9 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} @@ -2554,6 +2593,9 @@ packages: cpu: [x64] os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@rushstack/node-core-library@5.14.0': resolution: {integrity: sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==} peerDependencies: @@ -2963,6 +3005,9 @@ packages: '@types/diff-match-patch@1.0.36': resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/eslint-plugin-tailwindcss@3.17.0': + resolution: {integrity: sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -2978,6 +3023,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} @@ -3109,6 +3157,101 @@ packages: resolution: {integrity: sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + '@vitejs/plugin-vue@5.1.4': resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3510,10 +3653,34 @@ packages: resolution: {integrity: sha512-yOzOZcR9Tn7enTF66bqKorGGH0F36vcPaSWg8fO0c0UYb3LX3VMXj5ZxEqQLNOecAhlRJ7wYZja5i4jTlnbIfQ==} engines: {node: '>=4'} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3535,6 +3702,10 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -3552,6 +3723,10 @@ packages: resolution: {integrity: sha512-TnclbJ0482ydRenzrR9FIbqalHScBBdQTIXv8tVunhYx8dq7E0Eq5v5CSAo67YmLXNbx5jCstHcLZDJ33iONDw==} engines: {node: '>=18.2.0'} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + axios@1.11.0: resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} @@ -3818,6 +3993,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -3888,6 +4067,11 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3926,6 +4110,18 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -3933,6 +4129,14 @@ packages: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} engines: {node: '>=18'} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -3995,6 +4199,10 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -4029,6 +4237,10 @@ packages: resolution: {integrity: sha512-7SCDfnQtBObcngVXNPZcnxGxqqPTK4UqeXeKAch+RGH5qpqadWbV9FmN71x9Bb4tTs0TNFb4FT/4Kz4P4Cjqcw==} engines: {node: '>=6.0.0'} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + doctypes@1.1.0: resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} @@ -4156,6 +4368,10 @@ packages: error-stack-parser-es@0.1.5: resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==} + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -4175,6 +4391,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + es-toolkit@1.39.10: resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} @@ -4229,11 +4453,80 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-import-context@0.1.9: + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@4.4.4: + resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} + engines: {node: ^16.17.0 || >=18.6.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import-x@4.16.1: + resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/utils': ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + eslint-import-resolver-node: '*' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + eslint-import-resolver-node: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint-plugin-prettier@5.5.4: resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - '@types/eslint': '>=8.0.0' + '@types/eslint': '*' eslint: '>=8.0.0' eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' prettier: '>=3.0.0' @@ -4250,6 +4543,12 @@ packages: eslint: '>=8' storybook: ^9.1.6 + eslint-plugin-tailwindcss@4.0.0-beta.0: + resolution: {integrity: sha512-WWCajZgQu38Sd67ZCl2W6i3MRzqB0d+H8s4qV9iB6lBJbsDOIpIlj6R1Fj2FXkoWErbo05pZnZYbCGIU9o/DsA==} + engines: {node: '>=18.12.0'} + peerDependencies: + tailwindcss: ^3.4.0 || ^4.0.0 + eslint-plugin-unused-imports@4.2.0: resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==} peerDependencies: @@ -4478,6 +4777,10 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -4533,10 +4836,21 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + fuse.js@7.0.0: resolution: {integrity: sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==} engines: {node: '>=10'} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4565,6 +4879,13 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + get-tsconfig@4.7.5: resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} @@ -4609,6 +4930,10 @@ packages: resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -4640,6 +4965,10 @@ packages: resolution: {integrity: sha512-/zyxHbXriYJ8b9Urh43ILk/jd9tC07djURnJuAimJ3tJCOLOzOUp7dEHDwJOZyzROlrrooUhr/0INZIDBj1Bjw==} engines: {node: '>=18.0.0'} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -4647,6 +4976,10 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -4786,17 +5119,52 @@ packages: react-devtools-core: optional: true + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -4818,6 +5186,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -4830,6 +5202,10 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -4860,10 +5236,22 @@ packages: is-language-code@3.1.0: resolution: {integrity: sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -4890,6 +5278,14 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4898,6 +5294,18 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -4906,6 +5314,18 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} @@ -5044,6 +5464,10 @@ packages: 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 + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5660,10 +6084,30 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -5709,6 +6153,10 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + oxc-resolver@11.6.1: resolution: {integrity: sha512-WQgmxevT4cM5MZ9ioQnEwJiHpPzbvntV5nInGAKo9NQZzegcOonHvcVcnkYqld7bTG35UFHEKeF7VwwsmA3cZg==} @@ -5863,6 +6311,10 @@ packages: engines: {node: '>=18'} hasBin: true + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-html@1.8.0: resolution: {integrity: sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ==} engines: {node: ^12 || >=14} @@ -6118,6 +6570,10 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + regenerate-unicode-properties@10.2.0: resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} engines: {node: '>=4'} @@ -6125,6 +6581,10 @@ packages: regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + regexpu-core@6.2.0: resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} engines: {node: '>=4'} @@ -6229,12 +6689,24 @@ packages: rxjs-interop@2.0.0: resolution: {integrity: sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -6267,6 +6739,14 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -6278,6 +6758,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -6333,6 +6829,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash-x@0.2.0: + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -6346,6 +6846,10 @@ packages: std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + storybook@9.1.6: resolution: {integrity: sha512-iIcMaDKkjR5nN+JYBy9hhoxZhjX4TXhyJgUBed+toJOlfrl+QvxpBjImAi7qKyLR3hng3uoigEP0P8+vYtXpOg==} hasBin: true @@ -6374,6 +6878,18 @@ packages: string.fromcodepoint@0.2.1: resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -6474,6 +6990,11 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} + tailwind-api-utils@1.0.3: + resolution: {integrity: sha512-KpzUHkH1ug1sq4394SLJX38ZtpeTiqQ1RVyFTTSY2XuHsNSTWUkRo108KmyyrMWdDbQrLYkSHaNKj/a3bmA4sQ==} + peerDependencies: + tailwindcss: ^3.3.0 || ^4.0.0 || ^4.0.0-beta + tailwind-merge@2.6.0: resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} @@ -6595,6 +7116,9 @@ packages: ts-map@1.0.3: resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -6622,6 +7146,22 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + 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==} + engines: {node: '>= 0.4'} + typescript-eslint@8.44.0: resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6654,6 +7194,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -6743,6 +7287,9 @@ packages: resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} engines: {node: '>=18.12.0'} + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -6906,6 +7453,9 @@ packages: vue-component-type-helpers@3.1.0: resolution: {integrity: sha512-cC1pYNRZkSS1iCvdlaMbbg2sjDwxX098FucEjtz9Yig73zYjWzQsnMe5M9H8dRNv55hAIDGUI29hF2BEUA4FMQ==} + vue-component-type-helpers@3.1.1: + resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==} + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -7035,6 +7585,22 @@ packages: when-exit@2.1.4: resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -8103,6 +8669,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -8901,6 +9469,13 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@tybys/wasm-util': 0.10.0 + optional: true + '@napi-rs/wasm-runtime@0.2.4': dependencies: '@emnapi/core': 1.4.5 @@ -9350,6 +9925,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true + '@rtsao/scc@1.1.0': + optional: true + '@rushstack/node-core-library@5.14.0(@types/node@20.14.10)': dependencies: ajv: 8.13.0 @@ -9484,7 +10062,7 @@ snapshots: storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) type-fest: 2.19.0 vue: 3.5.13(typescript@5.9.2) - vue-component-type-helpers: 3.1.0 + vue-component-type-helpers: 3.1.1 '@swc/helpers@0.5.17': dependencies: @@ -9787,6 +10365,8 @@ snapshots: '@types/diff-match-patch@1.0.36': {} + '@types/eslint-plugin-tailwindcss@3.17.0': {} + '@types/estree@1.0.5': {} '@types/estree@1.0.8': {} @@ -9804,6 +10384,9 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': + optional: true + '@types/jsonfile@6.1.4': dependencies: '@types/node': 20.14.10 @@ -9972,6 +10555,65 @@ snapshots: '@typescript-eslint/types': 8.44.0 eslint-visitor-keys: 4.2.1 + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + '@vitejs/plugin-vue@5.1.4(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': dependencies: vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) @@ -10137,7 +10779,7 @@ snapshots: '@vue/shared': 3.5.13 estree-walker: 2.0.2 magic-string: 0.30.19 - postcss: 8.5.1 + postcss: 8.5.6 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.13': @@ -10460,8 +11102,64 @@ snapshots: arr-rotate@1.0.0: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + optional: true + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + optional: true + array-union@2.1.0: {} + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + optional: true + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + optional: true + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + optional: true + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + optional: true + asap@2.0.6: {} assert-never@1.4.0: {} @@ -10480,6 +11178,9 @@ snapshots: astral-regex@2.0.0: {} + async-function@1.0.0: + optional: true + async@3.2.5: {} asynckit@0.4.0: {} @@ -10496,6 +11197,11 @@ snapshots: '@babel/runtime': 7.28.4 tslib: 2.8.1 + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + optional: true + axios@1.11.0: dependencies: follow-redirects: 1.15.6 @@ -10780,6 +11486,8 @@ snapshots: commander@8.3.0: {} + comment-parser@1.4.1: {} + compare-versions@6.1.1: {} concat-map@0.0.1: {} @@ -10856,6 +11564,11 @@ snapshots: crelt@1.0.6: {} + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -10895,12 +11608,38 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + optional: true + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + optional: true + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + optional: true + de-indent@1.0.2: {} debounce-fn@6.0.0: dependencies: mimic-function: 5.0.1 + debug@3.2.7: + dependencies: + ms: 2.1.3 + optional: true + debug@4.3.7: dependencies: ms: 2.1.3 @@ -10944,6 +11683,13 @@ snapshots: define-lazy-prop@3.0.0: {} + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + defu@6.1.4: {} delayed-stream@1.0.0: {} @@ -10975,6 +11721,11 @@ snapshots: unescape-js: 1.1.4 utf8: 3.0.0 + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + optional: true + doctypes@1.1.0: {} dom-accessibility-api@0.5.16: {} @@ -11096,6 +11847,64 @@ snapshots: error-stack-parser-es@0.1.5: {} + es-abstract@1.24.0: + 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-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-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + 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.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 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + optional: true + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -11113,6 +11922,18 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + optional: true + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + optional: true + es-toolkit@1.39.10: {} esbuild-register@3.6.0(esbuild@0.25.5): @@ -11197,6 +12018,98 @@ snapshots: dependencies: eslint: 9.35.0(jiti@2.4.2) + eslint-import-context@0.1.9(unrs-resolver@1.11.1): + dependencies: + get-tsconfig: 4.10.1 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.11.1 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + optional: true + + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.4.2)): + dependencies: + debug: 4.4.3 + eslint: 9.35.0(jiti@2.4.2) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash-x: 0.2.0 + tinyglobby: 0.2.14 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0(jiti@2.4.2)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + optional: true + + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)): + dependencies: + '@typescript-eslint/types': 8.44.0 + comment-parser: 1.4.1 + debug: 4.4.3 + eslint: 9.35.0(jiti@2.4.2) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + is-glob: 4.0.3 + minimatch: 10.0.3 + semver: 7.7.2 + stable-hash-x: 0.2.0 + unrs-resolver: 1.11.1 + optionalDependencies: + '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.35.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + 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 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + optional: true + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2): dependencies: eslint: 9.35.0(jiti@2.4.2) @@ -11215,6 +12128,14 @@ snapshots: - supports-color - typescript + eslint-plugin-tailwindcss@4.0.0-beta.0(tailwindcss@4.1.12): + dependencies: + fast-glob: 3.3.3 + postcss: 8.5.6 + synckit: 0.11.11 + tailwind-api-utils: 1.0.3(tailwindcss@4.1.12) + tailwindcss: 4.1.12 + eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)): dependencies: eslint: 9.35.0(jiti@2.4.2) @@ -11518,6 +12439,11 @@ snapshots: follow-redirects@1.15.6: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + optional: true + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -11576,8 +12502,24 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + optional: true + + functions-have-names@1.2.3: + optional: true + fuse.js@7.0.0: {} + generator-function@2.0.1: + optional: true + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -11609,6 +12551,17 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + get-tsconfig@4.7.5: dependencies: resolve-pkg-maps: 1.0.0 @@ -11659,6 +12612,12 @@ snapshots: globals@16.4.0: {} + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -11693,12 +12652,20 @@ snapshots: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 + has-bigints@1.1.0: + optional: true + has-flag@4.0.0: {} has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + optional: true + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -11838,16 +12805,70 @@ snapshots: - bufferutil - utf-8-validate + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + optional: true + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + optional: true + is-arrayish@0.2.1: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + optional: true + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + optional: true + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + optional: true + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.2 + + is-callable@1.2.7: + optional: true + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + optional: true + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + optional: true + is-docker@2.2.1: {} is-docker@3.0.0: {} @@ -11861,6 +12882,11 @@ snapshots: is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + optional: true + is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@4.0.0: {} @@ -11869,6 +12895,15 @@ snapshots: dependencies: get-east-asian-width: 1.3.0 + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + optional: true + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -11892,8 +12927,20 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 + is-map@2.0.3: + optional: true + + is-negative-zero@2.0.3: + optional: true + is-npm@6.0.0: {} + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + optional: true + is-number@7.0.0: {} is-path-inside@4.0.0: {} @@ -11913,14 +12960,54 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-set@2.0.3: + optional: true + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + optional: true + is-stream@3.0.0: {} is-stream@4.0.1: {} + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + optional: true + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + optional: true + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + optional: true + is-unicode-supported@0.1.0: {} is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: + optional: true + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + optional: true + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + optional: true + is-what@4.1.16: {} is-wsl@2.2.0: @@ -12068,6 +13155,11 @@ snapshots: jsonify: 0.0.1 object-keys: 1.1.1 + json5@1.0.2: + dependencies: + minimist: 1.2.8 + optional: true + json5@2.2.3: {} jsonc-eslint-parser@2.4.0: @@ -12881,8 +13973,44 @@ snapshots: object-assign@4.1.1: {} + object-inspect@1.13.4: + optional: true + object-keys@1.1.1: {} + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + optional: true + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + optional: true + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + optional: true + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + optional: true + ohash@2.0.11: {} once@1.4.0: @@ -12947,6 +14075,13 @@ snapshots: orderedmap@2.1.1: {} + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + optional: true + oxc-resolver@11.6.1: dependencies: napi-postinstall: 0.3.3 @@ -13099,6 +14234,9 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + possible-typed-array-names@1.1.0: + optional: true + postcss-html@1.8.0: dependencies: htmlparser2: 8.0.2 @@ -13458,12 +14596,34 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + optional: true + regenerate-unicode-properties@10.2.0: dependencies: regenerate: 1.4.2 regenerate@1.4.2: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + optional: true + regexpu-core@6.2.0: dependencies: regenerate: 1.4.2 @@ -13607,10 +14767,32 @@ snapshots: rxjs-interop@2.0.0: {} + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + optional: true + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + optional: true + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + optional: true + safer-buffer@2.1.2: {} saxes@6.0.0: @@ -13641,6 +14823,21 @@ snapshots: gopd: 1.2.0 has-property-descriptors: 1.0.2 + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + optional: true + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + optional: true + setimmediate@1.0.5: {} shebang-command@2.0.0: @@ -13649,6 +14846,38 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + optional: true + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + optional: true + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + optional: true + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + optional: true + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -13699,6 +14928,8 @@ snapshots: sprintf-js@1.0.3: {} + stable-hash-x@0.2.0: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -13713,6 +14944,12 @@ snapshots: std-env@3.9.0: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + optional: true + storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): dependencies: '@storybook/global': 5.0.0 @@ -13759,6 +14996,32 @@ snapshots: string.fromcodepoint@0.2.1: {} + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + optional: true + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + optional: true + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + optional: true + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -13891,6 +15154,13 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + tailwind-api-utils@1.0.3(tailwindcss@4.1.12): + dependencies: + enhanced-resolve: 5.18.3 + jiti: 2.5.1 + local-pkg: 1.1.2 + tailwindcss: 4.1.12 + tailwind-merge@2.6.0: {} tailwindcss-primeui@0.6.1(tailwindcss@4.1.12): @@ -13998,6 +15268,14 @@ snapshots: ts-map@1.0.3: {} + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + optional: true + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -14023,6 +15301,43 @@ snapshots: type-fest@4.41.0: {} + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + optional: true + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + optional: true + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + 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 + optional: true + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + 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 + optional: true + typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2): dependencies: '@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) @@ -14046,6 +15361,14 @@ snapshots: uint8array-extras@1.5.0: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + optional: true + undici-types@5.26.5: {} unescape-js@1.1.4: @@ -14140,6 +15463,30 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.3 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.1.3(browserslist@4.25.3): dependencies: browserslist: 4.25.3 @@ -14359,6 +15706,8 @@ snapshots: vue-component-type-helpers@3.1.0: {} + vue-component-type-helpers@3.1.1: {} + vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)): dependencies: vue: 3.5.13(typescript@5.9.2) @@ -14481,6 +15830,51 @@ snapshots: when-exit@2.1.4: {} + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + optional: true + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + optional: true + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + optional: true + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + optional: true + which@1.3.1: dependencies: isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index afa1d2844f..f3b1ce3763 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,60 +3,19 @@ packages: - packages/** catalog: - # Core frameworks - typescript: ^5.9.2 - vue: ^3.5.13 - - # Build tools + '@alloc/quick-lru': ^5.2.0 + '@eslint/js': ^9.35.0 + '@iconify-json/lucide': ^1.1.178 + '@iconify/json': ^2.2.380 + '@iconify/tailwind': ^1.1.3 + '@intlify/eslint-plugin-vue-i18n': ^4.1.0 + '@lobehub/i18n-cli': ^1.25.1 '@nx/eslint': 21.4.1 '@nx/playwright': 21.4.1 '@nx/storybook': 21.4.1 '@nx/vite': 21.4.1 - nx: 21.4.1 - tsx: ^4.15.6 - vite: ^5.4.19 - '@vitejs/plugin-vue': ^5.1.4 - 'vite-plugin-dts': ^4.5.4 - vue-tsc: ^3.0.7 - - # Testing - 'happy-dom': ^15.11.0 - jsdom: ^26.1.0 '@pinia/testing': ^0.1.5 '@playwright/test': ^1.52.0 - '@vitest/coverage-v8': ^3.2.4 - '@vitest/ui': ^3.0.0 - vitest: ^3.2.4 - '@vue/test-utils': ^2.4.6 - - # Linting & Formatting - '@eslint/js': ^9.35.0 - eslint: ^9.34.0 - 'eslint-config-prettier': ^10.1.8 - 'eslint-plugin-prettier': ^5.5.4 - 'eslint-plugin-storybook': ^9.1.6 - 'eslint-plugin-unused-imports': ^4.2.0 - 'eslint-plugin-vue': ^10.4.0 - globals: ^15.9.0 - '@intlify/eslint-plugin-vue-i18n': ^4.1.0 - postcss-html: ^1.8.0 - prettier: ^3.3.2 - stylelint: ^16.24.0 - 'typescript-eslint': ^8.44.0 - 'vue-eslint-parser': ^10.2.0 - - # Vue ecosystem - '@sentry/vue': ^8.48.0 - '@vueuse/core': ^11.0.0 - '@vueuse/integrations': ^13.9.0 - 'vite-plugin-html': ^3.2.2 - 'vite-plugin-vue-devtools': ^7.7.6 - pinia: ^2.1.7 - 'vue-i18n': ^9.14.3 - 'vue-router': ^4.4.3 - vuefire: ^3.2.1 - - # PrimeVue UI framework '@primeuix/forms': 0.0.2 '@primeuix/styled': 0.3.2 '@primeuix/utils': ^0.3.2 @@ -64,58 +23,87 @@ catalog: '@primevue/forms': ^4.2.5 '@primevue/icons': 4.2.5 '@primevue/themes': ^4.2.5 - primeicons: ^7.0.0 - primevue: ^4.2.5 - - # Tailwind CSS and design - '@iconify/json': ^2.2.380 - '@iconify-json/lucide': ^1.1.178 - '@iconify/tailwind': ^1.1.3 - '@tailwindcss/vite': ^4.1.12 - tailwindcss: ^4.1.12 - 'tailwindcss-primeui': ^0.6.1 - 'tw-animate-css': ^1.3.8 - 'unplugin-icons': ^0.22.0 - 'unplugin-vue-components': ^0.28.0 - - # Storybook + '@sentry/vue': ^8.48.0 '@storybook/addon-docs': ^9.1.1 - storybook: ^9.1.6 '@storybook/vue3': ^9.1.1 '@storybook/vue3-vite': ^9.1.1 - - # Data and validation - algoliasearch: ^5.21.0 - axios: ^1.8.2 - firebase: ^11.6.0 - yjs: ^13.6.27 - zod: ^3.23.8 - 'zod-validation-error': ^3.3.0 - - # Dev tools - dotenv: ^16.4.5 - husky: ^9.0.11 - jiti: 2.4.2 - knip: ^5.62.0 - 'lint-staged': ^15.2.7 - - # Type definitions + '@tailwindcss/vite': ^4.1.12 + '@trivago/prettier-plugin-sort-imports': ^5.2.0 + '@types/eslint-plugin-tailwindcss': ^3.17.0 '@types/fs-extra': ^11.0.4 '@types/jsdom': ^21.1.7 '@types/node': ^20.14.8 '@types/semver': ^7.7.0 '@types/three': ^0.169.0 - 'vue-component-type-helpers': ^3.0.7 - 'zod-to-json-schema': ^3.24.1 + '@vitejs/plugin-vue': ^5.1.4 + '@vitest/coverage-v8': ^3.2.4 + '@vitest/ui': ^3.0.0 + '@vue/test-utils': ^2.4.6 + '@vueuse/core': ^11.0.0 + '@vueuse/integrations': ^13.9.0 + algoliasearch: ^5.21.0 + axios: ^1.8.2 + cross-env: ^10.1.0 + dotenv: ^16.4.5 + eslint: ^9.34.0 + eslint-config-prettier: ^10.1.8 + eslint-import-resolver-typescript: ^4.4.4 + eslint-plugin-import-x: ^4.16.1 + eslint-plugin-prettier: ^5.5.4 + eslint-plugin-storybook: ^9.1.6 + eslint-plugin-tailwindcss: 4.0.0-beta.0 + eslint-plugin-unused-imports: ^4.2.0 + eslint-plugin-vue: ^10.4.0 + firebase: ^11.6.0 + globals: ^15.9.0 + happy-dom: ^15.11.0 + husky: ^9.0.11 + jiti: 2.4.2 + jsdom: ^26.1.0 + knip: ^5.62.0 + lint-staged: ^15.2.7 + nx: 21.4.1 + pinia: ^2.1.7 + postcss-html: ^1.8.0 + prettier: ^3.3.2 + primeicons: ^7.0.0 + primevue: ^4.2.5 + storybook: ^9.1.6 + stylelint: ^16.24.0 + tailwindcss: ^4.1.12 + tailwindcss-primeui: ^0.6.1 + tsx: ^4.15.6 + tw-animate-css: ^1.3.8 + typescript: ^5.9.2 + typescript-eslint: ^8.44.0 + unplugin-icons: ^0.22.0 + unplugin-vue-components: ^0.28.0 + vite: ^5.4.19 + vite-plugin-dts: ^4.5.4 + vite-plugin-html: ^3.2.2 + vite-plugin-vue-devtools: ^7.7.6 + vitest: ^3.2.4 + vue: ^3.5.13 + vue-component-type-helpers: ^3.0.7 + vue-eslint-parser: ^10.2.0 + vue-i18n: ^9.14.3 + vue-router: ^4.4.3 + vue-tsc: ^3.0.7 + vuefire: ^3.2.1 + yjs: ^13.6.27 + zod: ^3.23.8 + zod-to-json-schema: ^3.24.1 + zod-validation-error: ^3.3.0 - # i18n - '@alloc/quick-lru': ^5.2.0 - '@lobehub/i18n-cli': ^1.25.1 - '@trivago/prettier-plugin-sort-imports': ^5.2.0 +cleanupUnusedCatalogs: true + +overrides: + '@types/eslint': '-' ignoredBuiltDependencies: - '@firebase/util' - protobufjs + - unrs-resolver - vue-demi onlyBuiltDependencies: diff --git a/src/App.vue b/src/App.vue index 6f6f10a1ea..c0c9fdd201 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,7 +2,7 @@ diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index e05a106197..6844f9a36b 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -5,7 +5,15 @@ :class="{ 'is-dragging': isDragging, 'is-docked': isDocked }" >
- +
@@ -26,6 +34,7 @@ import type { Ref } from 'vue' import { computed, inject, nextTick, onMounted, ref, watch } from 'vue' import { useSettingStore } from '@/platform/settings/settingStore' +import { cn } from '@/utils/tailwindUtil' import ComfyQueueButton from './ComfyQueueButton.vue' @@ -257,8 +266,4 @@ watch([isDragging, isOverlappingWithTopMenu], ([dragging, overlapping]) => { :deep(.p-panel-header) { display: none; } - -.drag-handle { - @apply w-3 h-max; -} diff --git a/src/components/bottomPanel/BottomPanel.vue b/src/components/bottomPanel/BottomPanel.vue index 0ed71bffa5..cffa74f76a 100644 --- a/src/components/bottomPanel/BottomPanel.vue +++ b/src/components/bottomPanel/BottomPanel.vue @@ -1,17 +1,17 @@