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' - - - 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: Build & Install ComfyUI_frontend + 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 dependencies shell: bash - working-directory: ComfyUI_frontend - run: | - pnpm install --frozen-lockfile - pnpm build + run: pnpm install --frozen-lockfile - - name: Start ComfyUI server + - name: Build ComfyUI_frontend + if: ${{ inputs.include_build_step == 'true' }} 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/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..f0fe1f3c2d --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,21 @@ +# GitHub Workflows + +## Naming Convention + +Workflow files follow a consistent naming pattern: `-.yaml` + +### Category Prefixes + +| Prefix | Purpose | Example | +|--------|---------|---------| +| `ci-` | Testing, linting, validation | `ci-tests-e2e.yaml` | +| `release-` | Version management, publishing | `release-version-bump.yaml` | +| `pr-` | PR automation (triggered by labels) | `pr-claude-review.yaml` | +| `types-` | TypeScript type generation | `types-registry-api.yaml` | +| `i18n-` | Internationalization updates | `i18n-update-core.yaml` | + +## Documentation + +Each workflow file contains comments explaining its purpose, triggers, and behavior. For specific details about what each workflow does, refer to the comments at the top of each `.yaml` file. + +For GitHub Actions documentation, see [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows). \ No newline at end of file diff --git a/.github/workflows/validate-json.yaml b/.github/workflows/ci-json-validation.yaml similarity index 55% rename from .github/workflows/validate-json.yaml rename to .github/workflows/ci-json-validation.yaml index 2986d23ed1..df86a9e6ad 100644 --- a/.github/workflows/validate-json.yaml +++ b/.github/workflows/ci-json-validation.yaml @@ -1,4 +1,7 @@ -name: Validate JSON +name: "CI: JSON Validation" + +# Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) +# Uses jq to ensure valid JSON syntax across configuration and data files on: push: diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/ci-lint-format.yaml similarity index 97% rename from .github/workflows/lint-and-format.yaml rename to .github/workflows/ci-lint-format.yaml index 1f20ab92ed..45e845eb56 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/ci-lint-format.yaml @@ -1,9 +1,13 @@ -name: Lint and Format +name: "CI: Lint Format" 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/devtools-python-check.yaml b/.github/workflows/ci-python-validation.yaml similarity index 93% rename from .github/workflows/devtools-python-check.yaml rename to .github/workflows/ci-python-validation.yaml index f0893e99d1..28870261d3 100644 --- a/.github/workflows/devtools-python-check.yaml +++ b/.github/workflows/ci-python-validation.yaml @@ -1,4 +1,4 @@ -name: Devtools Python Check +name: "CI: Python Validation" on: pull_request: diff --git a/.github/workflows/pr-playwright-deploy-forks.yaml b/.github/workflows/ci-tests-e2e-forks.yaml similarity index 96% rename from .github/workflows/pr-playwright-deploy-forks.yaml rename to .github/workflows/ci-tests-e2e-forks.yaml index 660fee77fa..690b2710d7 100644 --- a/.github/workflows/pr-playwright-deploy-forks.yaml +++ b/.github/workflows/ci-tests-e2e-forks.yaml @@ -1,8 +1,10 @@ -name: PR Playwright Deploy (Forks) +name: "CI: Tests E2E Forks" + +# Deploys test results from forked PRs (forks can't access deployment secrets) on: workflow_run: - workflows: ["Tests CI"] + workflows: ["Tests E2E CI"] types: [requested, completed] env: diff --git a/.github/workflows/tests-ci.yaml b/.github/workflows/ci-tests-e2e.yaml similarity index 55% rename from .github/workflows/tests-ci.yaml rename to .github/workflows/ci-tests-e2e.yaml index 1e069ea11f..a6ed8e472b 100644 --- a/.github/workflows/tests-ci.yaml +++ b/.github/workflows/ci-tests-e2e.yaml @@ -1,4 +1,8 @@ -name: Tests CI +name: "CI: Tests E2E" + +# End-to-end testing with Playwright across multiple browsers and configurations +# Runs on main branches and PRs, skips WIP branches +# Generates test reports and deploys them to Cloudflare Pages for review on: push: @@ -7,70 +11,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 - uses: actions/checkout@v5 - with: - repository: 'comfyanonymous/ComfyUI' - path: 'ComfyUI' - ref: master - - - name: Checkout ComfyUI_frontend + - name: Checkout repository 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 + # 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: - 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 +56,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 +103,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 +133,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 +148,19 @@ jobs: runs-on: ubuntu-latest if: ${{ !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,13 +171,12 @@ 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: Upload HTML report 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) @@ -272,11 +192,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 }} @@ -287,7 +207,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] @@ -299,23 +219,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/pr-storybook-deploy-forks.yaml b/.github/workflows/ci-tests-storybook-forks.yaml similarity index 95% rename from .github/workflows/pr-storybook-deploy-forks.yaml rename to .github/workflows/ci-tests-storybook-forks.yaml index da27867c47..b71683023d 100644 --- a/.github/workflows/pr-storybook-deploy-forks.yaml +++ b/.github/workflows/ci-tests-storybook-forks.yaml @@ -1,8 +1,10 @@ -name: PR Storybook Deploy (Forks) +name: "CI: Tests Storybook Forks" + +# Deploys Storybook previews from forked PRs (forks can't access deployment secrets) on: workflow_run: - workflows: ['Storybook and Chromatic CI'] + workflows: ['Tests Storybook CI'] types: [requested, completed] env: diff --git a/.github/workflows/storybook-and-chromatic-ci.yaml b/.github/workflows/ci-tests-storybook.yaml similarity index 97% rename from .github/workflows/storybook-and-chromatic-ci.yaml rename to .github/workflows/ci-tests-storybook.yaml index bfac965306..3e2c6a46f8 100644 --- a/.github/workflows/storybook-and-chromatic-ci.yaml +++ b/.github/workflows/ci-tests-storybook.yaml @@ -1,6 +1,8 @@ -name: Storybook and Chromatic CI +name: "CI: Tests Storybook" -# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ ) +# Builds Storybook and runs visual regression testing via Chromatic +# Deploys Storybook previews to Cloudflare Pages for PR review +# Reference: https://www.chromatic.com/docs/github-actions/ on: workflow_dispatch: # Allow manual triggering diff --git a/.github/workflows/vitest-tests.yaml b/.github/workflows/ci-tests-unit.yaml similarity index 84% rename from .github/workflows/vitest-tests.yaml rename to .github/workflows/ci-tests-unit.yaml index 46155d9121..9ccb2f3c62 100644 --- a/.github/workflows/vitest-tests.yaml +++ b/.github/workflows/ci-tests-unit.yaml @@ -1,4 +1,7 @@ -name: Vitest Tests +name: "CI: Tests Unit" + +# Unit and component testing with Vitest +# Runs on main branches and PRs, skips WIP branches on: push: @@ -6,6 +9,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/.github/workflows/update-locales.yaml b/.github/workflows/i18n-update-core.yaml similarity index 76% rename from .github/workflows/update-locales.yaml rename to .github/workflows/i18n-update-core.yaml index 6ee0933121..da17374809 100644 --- a/.github/workflows/update-locales.yaml +++ b/.github/workflows/i18n-update-core.yaml @@ -1,4 +1,7 @@ -name: Update Locales +name: i18n Update Core + +# Generates and updates translations for core ComfyUI components using OpenAI +# Runs manually or automatically on version-bump PRs to keep translations current on: # Manual dispatch for urgent translation updates @@ -16,36 +19,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 +59,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-locales-for-given-custom-node-repository.yaml b/.github/workflows/i18n-update-custom-nodes.yaml similarity index 71% rename from .github/workflows/update-locales-for-given-custom-node-repository.yaml rename to .github/workflows/i18n-update-custom-nodes.yaml index ec085eab59..61076f0317 100644 --- a/.github/workflows/update-locales-for-given-custom-node-repository.yaml +++ b/.github/workflows/i18n-update-custom-nodes.yaml @@ -1,4 +1,4 @@ -name: Update Locales for given custom node repository +name: i18n Update Custom Nodes on: workflow_dispatch: @@ -21,90 +21,64 @@ jobs: update-locales: runs-on: ubuntu-latest steps: - - name: Checkout ComfyUI - uses: actions/checkout@v5 - with: - repository: comfyanonymous/ComfyUI - path: ComfyUI - ref: master - - name: Checkout ComfyUI_frontend + - 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: 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-node-definitions-locales.yaml b/.github/workflows/i18n-update-nodes.yaml similarity index 78% rename from .github/workflows/update-node-definitions-locales.yaml rename to .github/workflows/i18n-update-nodes.yaml index b063159ddf..0b9f1534d3 100644 --- a/.github/workflows/update-node-definitions-locales.yaml +++ b/.github/workflows/i18n-update-nodes.yaml @@ -1,4 +1,4 @@ -name: Update Node Definitions Locales +name: i18n Update Nodes on: workflow_dispatch: @@ -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/auto-backport.yaml b/.github/workflows/pr-backport.yaml similarity index 99% rename from .github/workflows/auto-backport.yaml rename to .github/workflows/pr-backport.yaml index 20eadc0c9e..13e6dd74e8 100644 --- a/.github/workflows/auto-backport.yaml +++ b/.github/workflows/pr-backport.yaml @@ -1,4 +1,4 @@ -name: Auto Backport +name: PR Backport on: pull_request_target: diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/pr-claude-review.yaml similarity index 82% rename from .github/workflows/claude-pr-review.yml rename to .github/workflows/pr-claude-review.yaml index 08ad707274..292fe676ce 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/pr-claude-review.yaml @@ -1,4 +1,7 @@ -name: Claude PR Review +name: PR Claude Review + +# AI-powered code review triggered by adding the "claude-review" label to a PR +# Provides comprehensive analysis of code changes, architecture, and security permissions: contents: read @@ -11,6 +14,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 +80,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 +93,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/pr-playwright-snapshots.yaml b/.github/workflows/pr-playwright-snapshots.yaml new file mode 100644 index 0000000000..b415542539 --- /dev/null +++ b/.github/workflows/pr-playwright-snapshots.yaml @@ -0,0 +1,108 @@ +# Setting test expectation screenshots for Playwright +name: PR Playwright Snapshots + +on: + pull_request: + types: [labeled] + 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_name == 'issue_comment' && + ( + github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR' + ) && + 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 + 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 + - name: Run Playwright tests and update snapshots + id: playwright-tests + run: pnpm exec playwright test --update-snapshots + continue-on-error: true + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: ./playwright-report/ + retention-days: 30 + - name: Debugging info + run: | + echo "PR: ${{ github.event.issue.number }}" + echo "Branch: ${{ steps.get-branch.outputs.branch }}" + git status + - name: Commit updated expectations + run: | + git config --global user.name 'github-actions' + git config --global user.email 'github-actions@github.com' + git add browser_tests + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "[automated] Update test expectations" + git push origin ${{ steps.get-branch.outputs.branch }} + fi + + - 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/create-release-branch.yaml b/.github/workflows/release-branch-create.yaml similarity index 99% rename from .github/workflows/create-release-branch.yaml rename to .github/workflows/release-branch-create.yaml index 7891a845db..992e779ddb 100644 --- a/.github/workflows/create-release-branch.yaml +++ b/.github/workflows/release-branch-create.yaml @@ -1,4 +1,4 @@ -name: Create Release Branch +name: Release Branch Create on: pull_request: diff --git a/.github/workflows/create-release-draft.yaml b/.github/workflows/release-draft-create.yaml similarity index 98% rename from .github/workflows/create-release-draft.yaml rename to .github/workflows/release-draft-create.yaml index c359e3da49..240a89f1f2 100644 --- a/.github/workflows/create-release-draft.yaml +++ b/.github/workflows/release-draft-create.yaml @@ -1,4 +1,4 @@ -name: Create Release Draft +name: Release Draft Create on: pull_request: @@ -126,7 +126,7 @@ jobs: publish_types: needs: build - uses: ./.github/workflows/publish-frontend-types.yaml + uses: ./.github/workflows/release-npm-types.yaml with: version: ${{ needs.build.outputs.version }} ref: ${{ github.event.pull_request.merge_commit_sha }} diff --git a/.github/workflows/publish-frontend-types.yaml b/.github/workflows/release-npm-types.yaml similarity index 99% rename from .github/workflows/publish-frontend-types.yaml rename to .github/workflows/release-npm-types.yaml index 142a22a93c..23f0cc016b 100644 --- a/.github/workflows/publish-frontend-types.yaml +++ b/.github/workflows/release-npm-types.yaml @@ -1,4 +1,4 @@ -name: Publish Frontend Types +name: Release NPM Types on: workflow_dispatch: diff --git a/.github/workflows/create-dev-pypi-package.yaml b/.github/workflows/release-pypi-dev.yaml similarity index 98% rename from .github/workflows/create-dev-pypi-package.yaml rename to .github/workflows/release-pypi-dev.yaml index b592a8371e..88675e82e3 100644 --- a/.github/workflows/create-dev-pypi-package.yaml +++ b/.github/workflows/release-pypi-dev.yaml @@ -1,4 +1,4 @@ -name: Create Dev PyPI Package +name: Release PyPI Dev on: workflow_dispatch: diff --git a/.github/workflows/version-bump.yaml b/.github/workflows/release-version-bump.yaml similarity index 91% rename from .github/workflows/version-bump.yaml rename to .github/workflows/release-version-bump.yaml index 4073729db8..d0b530691c 100644 --- a/.github/workflows/version-bump.yaml +++ b/.github/workflows/release-version-bump.yaml @@ -1,4 +1,7 @@ -name: Version Bump +name: Release Version Bump + +# Manual workflow to increment package version and create version commit +# Supports semantic versioning (patch, minor, major) and pre-release versions on: workflow_dispatch: diff --git a/.github/workflows/update-electron-types.yaml b/.github/workflows/types-electron-api.yaml similarity index 98% rename from .github/workflows/update-electron-types.yaml rename to .github/workflows/types-electron-api.yaml index 45d959b86d..b8595d1cb0 100644 --- a/.github/workflows/update-electron-types.yaml +++ b/.github/workflows/types-electron-api.yaml @@ -1,4 +1,4 @@ -name: Update Electron Types +name: Types Electron API on: workflow_dispatch: diff --git a/.github/workflows/update-comfyui-manager-api-types.yaml b/.github/workflows/types-manager-api.yaml similarity index 99% rename from .github/workflows/update-comfyui-manager-api-types.yaml rename to .github/workflows/types-manager-api.yaml index 7e307dfda6..af2cb2f9b5 100644 --- a/.github/workflows/update-comfyui-manager-api-types.yaml +++ b/.github/workflows/types-manager-api.yaml @@ -1,4 +1,4 @@ -name: Update ComfyUI-Manager API Types +name: Types Manager API on: # Manual trigger diff --git a/.github/workflows/update-comfy-registry-api-types.yaml b/.github/workflows/types-registry-api.yaml similarity index 99% rename from .github/workflows/update-comfy-registry-api-types.yaml rename to .github/workflows/types-registry-api.yaml index 41a4db9ab9..5cfd2fd7a4 100644 --- a/.github/workflows/update-comfy-registry-api-types.yaml +++ b/.github/workflows/types-registry-api.yaml @@ -1,4 +1,4 @@ -name: Update Comfy Registry API Types +name: Types Registry API on: # Manual trigger diff --git a/.github/workflows/update-playwright-expectations.yaml b/.github/workflows/update-playwright-expectations.yaml deleted file mode 100644 index 82b99baa87..0000000000 --- a/.github/workflows/update-playwright-expectations.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# Setting test expectation screenshots for Playwright -name: Update Playwright Expectations - -on: - pull_request: - types: [labeled] - issue_comment: - types: [created] - -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_name == 'issue_comment' && - ( - github.event.comment.author_association == 'OWNER' || - github.event.comment.author_association == 'MEMBER' || - github.event.comment.author_association == 'COLLABORATOR' - ) && - startsWith(github.event.comment.body, '/update-playwright') ) - steps: - - name: Initial Checkout - uses: actions/checkout@v5 - - name: Pull Request Checkout - run: gh pr checkout ${{ github.event.issue.number }} - if: github.event.issue.pull_request && github.event_name == 'issue_comment' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Frontend - uses: ./.github/actions/setup-frontend - - name: Setup Playwright - uses: ./.github/actions/setup-playwright - - name: Run Playwright tests and update snapshots - id: playwright-tests - run: pnpm exec playwright test --update-snapshots - continue-on-error: true - working-directory: ComfyUI_frontend - - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: ComfyUI_frontend/playwright-report/ - retention-days: 30 - - name: Debugging info - run: | - echo "PR: ${{ github.event.issue.number }}" - git status - working-directory: ComfyUI_frontend - - name: Commit updated expectations - run: | - git config --global user.name 'github-actions' - git config --global user.email 'github-actions@github.com' - git add browser_tests - git diff --cached --quiet || git commit -m "[automated] Update test expectations" - git push - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: ComfyUI_frontend 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/eslint.config.ts b/eslint.config.ts index 131c63ace6..4e19eba2e4 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -4,36 +4,67 @@ 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', + '.i18nrc.cjs', + 'components.d.ts', + 'lint-staged.config.js', + 'vitest.setup.ts', + '**/vite.config.*.timestamp*', + '**/vitest.config.*.timestamp*', 'packages/registry-types/src/comfyRegistryTypes.ts', + 'src/extensions/core/*', + 'src/scripts/*', 'src/types/generatedManagerTypes.ts', - '**/vite.config.*.timestamp*', - '**/vitest.config.*.timestamp*' + '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', @@ -42,47 +73,26 @@ export default defineConfig([ 'playwright.config.ts', 'playwright.i18n.config.ts' ] - }, - tsConfigRootDir: import.meta.dirname, - ecmaVersion: 2020, - sourceType: 'module', - extraFileExtensions - } - }, - settings: { - 'import/resolver': { - typescript: true, - node: true + } } } }, { 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 - } - }, - settings: { - 'import/resolver': { - typescript: true, - node: true - } + parserOptions: commonParserOptions } }, pluginJs.configs.recommended, - // eslint-disable-next-line import-x/no-named-as-default-member - 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'], @@ -114,6 +124,7 @@ export default defineConfig([ '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:/'], diff --git a/package.json b/package.json index 748ef48335..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", @@ -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:", @@ -73,6 +74,7 @@ "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 8bb996c7da..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 @@ -147,6 +150,9 @@ catalogs: 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 @@ -274,6 +280,9 @@ catalogs: specifier: ^3.3.0 version: 3.3.0 +overrides: + '@types/eslint': '-' + importers: .: @@ -489,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 @@ -537,6 +549,9 @@ importers: 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)) @@ -2990,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==} @@ -4508,7 +4526,7 @@ packages: 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' @@ -4525,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: @@ -6966,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==} @@ -10336,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': {} @@ -10748,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': @@ -12097,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) @@ -15115,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): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 936e0ce8b4..f3b1ce3763 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -29,6 +29,7 @@ catalog: '@storybook/vue3-vite': ^9.1.1 '@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 @@ -50,6 +51,7 @@ catalog: 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 @@ -95,6 +97,9 @@ catalog: cleanupUnusedCatalogs: true +overrides: + '@types/eslint': '-' + ignoredBuiltDependencies: - '@firebase/util' - protobufjs 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 @@