diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
index d4a309bf8..4405aad1f 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -1,6 +1,5 @@
name: Bug Report
description: 'Report something that is not working correctly'
-title: '[Bug]: '
labels: ['Potential Bug']
type: Bug
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml
index a32598374..0d8173b28 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yaml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yaml
@@ -1,7 +1,6 @@
name: Feature Request
description: Report a problem or limitation you're experiencing
-title: '[Feature]: '
-labels: ['enhancement']
+labels: []
type: Feature
body:
diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml
deleted file mode 100644
index b49eb544a..000000000
--- a/.github/workflows/pr-checks.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-name: PR Checks
-on:
- pull_request:
- types: [opened, edited, synchronize, reopened]
-
-permissions:
- contents: read
- pull-requests: read
-
-jobs:
- analyze:
- runs-on: ubuntu-latest
- outputs:
- should_run: ${{ steps.check-changes.outputs.should_run }}
- has_browser_tests: ${{ steps.check-coverage.outputs.has_browser_tests }}
- has_screen_recording: ${{ steps.check-recording.outputs.has_recording }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Ensure base branch is available
- run: |
- # Fetch the specific base commit to ensure it's available for git diff
- git fetch origin ${{ github.event.pull_request.base.sha }}
-
- - name: Check if significant changes exist
- id: check-changes
- run: |
- # Get list of changed files
- CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }})
-
- # Filter for src/ files
- SRC_FILES=$(echo "$CHANGED_FILES" | grep '^src/' || true)
-
- if [ -z "$SRC_FILES" ]; then
- echo "No src/ files changed"
- echo "should_run=false" >> "$GITHUB_OUTPUT"
- exit 0
- fi
-
- # Count lines changed in src files
- TOTAL_LINES=0
- for file in $SRC_FILES; do
- if [ -f "$file" ]; then
- # Count added lines (non-empty)
- ADDED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^+' | grep -v '^+++' | grep -v '^+$' | wc -l)
- # Count removed lines (non-empty)
- REMOVED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^-' | grep -v '^---' | grep -v '^-$' | wc -l)
- TOTAL_LINES=$((TOTAL_LINES + ADDED + REMOVED))
- fi
- done
-
- echo "Total lines changed in src/: $TOTAL_LINES"
-
- if [ $TOTAL_LINES -gt 3 ]; then
- echo "should_run=true" >> "$GITHUB_OUTPUT"
- else
- echo "should_run=false" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Check browser test coverage
- id: check-coverage
- if: steps.check-changes.outputs.should_run == 'true'
- run: |
- # Check if browser tests were updated
- BROWSER_TEST_CHANGES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | grep '^browser_tests/.*\.ts$' || true)
-
- if [ -n "$BROWSER_TEST_CHANGES" ]; then
- echo "has_browser_tests=true" >> "$GITHUB_OUTPUT"
- else
- echo "has_browser_tests=false" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Check for screen recording
- id: check-recording
- if: steps.check-changes.outputs.should_run == 'true'
- env:
- PR_BODY: ${{ github.event.pull_request.body }}
- run: |
- # Check PR body for screen recording
- # Check for GitHub user attachments or YouTube links
- if echo "$PR_BODY" | grep -qiE 'github\.com/user-attachments/assets/[a-f0-9-]+|youtube\.com/watch|youtu\.be/'; then
- echo "has_recording=true" >> "$GITHUB_OUTPUT"
- else
- echo "has_recording=false" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Final check and create results
- id: final-check
- if: always()
- run: |
- # Initialize results
- WARNINGS_JSON=""
-
- # Only run checks if should_run is true
- if [ "${{ steps.check-changes.outputs.should_run }}" == "true" ]; then
- # Check browser test coverage
- if [ "${{ steps.check-coverage.outputs.has_browser_tests }}" != "true" ]; then
- if [ -n "$WARNINGS_JSON" ]; then
- WARNINGS_JSON="${WARNINGS_JSON},"
- fi
- WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: E2E Test Coverage Missing**\\n\\nIf this PR modifies behavior that can be covered by browser-based E2E tests, those tests are required. PRs lacking applicable test coverage may not be reviewed until added. Please add or update browser tests to ensure code quality and prevent regressions.\"}"
- fi
-
- # Check screen recording
- if [ "${{ steps.check-recording.outputs.has_recording }}" != "true" ]; then
- if [ -n "$WARNINGS_JSON" ]; then
- WARNINGS_JSON="${WARNINGS_JSON},"
- fi
- WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: Visual Documentation Missing**\\n\\nIf this PR changes user-facing behavior, visual proof (screen recording or screenshot) is required. PRs without applicable visual documentation may not be reviewed until provided.\\nYou can add it by:\\n\\n- GitHub: Drag & drop media directly into the PR description\\n\\n- YouTube: Include a link to a short demo\"}"
- fi
- fi
-
- # Create results JSON
- if [ -n "$WARNINGS_JSON" ]; then
- # Create JSON with warnings
- cat > pr-check-results.json << EOF
- {
- "fails": [],
- "warnings": [$WARNINGS_JSON],
- "messages": [],
- "markdowns": []
- }
- EOF
- echo "failed=false" >> "$GITHUB_OUTPUT"
- else
- # Create JSON with success
- cat > pr-check-results.json << 'EOF'
- {
- "fails": [],
- "warnings": [],
- "messages": [],
- "markdowns": []
- }
- EOF
- echo "failed=false" >> "$GITHUB_OUTPUT"
- fi
-
- # Write PR metadata
- echo "${{ github.event.pull_request.number }}" > pr-number.txt
- echo "${{ github.event.pull_request.head.sha }}" > pr-sha.txt
-
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: pr-check-results-${{ github.run_id }}
- path: |
- pr-check-results.json
- pr-number.txt
- pr-sha.txt
- retention-days: 1
\ No newline at end of file
diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml
deleted file mode 100644
index 5ae8cf83a..000000000
--- a/.github/workflows/pr-comment.yml
+++ /dev/null
@@ -1,149 +0,0 @@
-name: PR Comment
-on:
- workflow_run:
- workflows: ["PR Checks"]
- types: [completed]
-
-permissions:
- pull-requests: write
- issues: write
- statuses: write
-
-jobs:
- comment:
- if: github.event.workflow_run.event == 'pull_request'
- runs-on: ubuntu-latest
- steps:
- - name: Download artifacts
- uses: actions/download-artifact@v4
- with:
- name: pr-check-results-${{ github.event.workflow_run.id }}
- path: /tmp/pr-artifacts
- github-token: ${{ secrets.GITHUB_TOKEN }}
- run-id: ${{ github.event.workflow_run.id }}
-
- - name: Post results
- uses: actions/github-script@v7
- with:
- script: |
- const fs = require('fs');
- const path = require('path');
-
- // Helper function to safely read files
- function safeReadFile(filePath) {
- try {
- if (!fs.existsSync(filePath)) return null;
- return fs.readFileSync(filePath, 'utf8').trim();
- } catch (e) {
- console.error(`Error reading ${filePath}:`, e);
- return null;
- }
- }
-
- // Read artifact files
- const artifactDir = '/tmp/pr-artifacts';
- const prNumber = safeReadFile(path.join(artifactDir, 'pr-number.txt'));
- const prSha = safeReadFile(path.join(artifactDir, 'pr-sha.txt'));
- const resultsJson = safeReadFile(path.join(artifactDir, 'pr-check-results.json'));
-
- // Validate PR number
- if (!prNumber || isNaN(parseInt(prNumber))) {
- throw new Error('Invalid or missing PR number');
- }
-
- // Parse and validate results
- let results;
- try {
- results = JSON.parse(resultsJson || '{}');
- } catch (e) {
- console.error('Failed to parse check results:', e);
-
- // Post error comment
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: parseInt(prNumber),
- body: `⚠️ PR checks failed to complete properly. Error parsing results: ${e.message}`
- });
- return;
- }
-
- // Format check messages
- const messages = [];
-
- if (results.fails && results.fails.length > 0) {
- messages.push('### ❌ Failures\n' + results.fails.map(f => f.message).join('\n\n'));
- }
-
- if (results.warnings && results.warnings.length > 0) {
- messages.push('### ⚠️ Warnings\n' + results.warnings.map(w => w.message).join('\n\n'));
- }
-
- if (results.messages && results.messages.length > 0) {
- messages.push('### 💬 Messages\n' + results.messages.map(m => m.message).join('\n\n'));
- }
-
- if (results.markdowns && results.markdowns.length > 0) {
- messages.push(...results.markdowns.map(m => m.message));
- }
-
- // Find existing bot comment
- const comments = await github.rest.issues.listComments({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: parseInt(prNumber)
- });
-
- const botComment = comments.data.find(comment =>
- comment.user.type === 'Bot' &&
- comment.body.includes('')
- );
-
- // Post comment if there are any messages
- if (messages.length > 0) {
- const body = messages.join('\n\n');
- const commentBody = `\n${body}`;
-
- if (botComment) {
- await github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: botComment.id,
- body: commentBody
- });
- } else {
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: parseInt(prNumber),
- body: commentBody
- });
- }
- } else {
- // No messages - delete existing comment if present
- if (botComment) {
- await github.rest.issues.deleteComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: botComment.id
- });
- }
- }
-
- // Set commit status based on failures
- if (prSha) {
- const hasFailures = results.fails && results.fails.length > 0;
- const hasWarnings = results.warnings && results.warnings.length > 0;
- await github.rest.repos.createCommitStatus({
- owner: context.repo.owner,
- repo: context.repo.repo,
- sha: prSha,
- state: hasFailures ? 'failure' : 'success',
- context: 'pr-checks',
- description: hasFailures
- ? `${results.fails.length} check(s) failed`
- : hasWarnings
- ? `${results.warnings.length} warning(s)`
- : 'All checks passed'
- });
- }
\ No newline at end of file
diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml
index 39411ade3..45a84d23a 100644
--- a/.github/workflows/test-ui.yaml
+++ b/.github/workflows/test-ui.yaml
@@ -4,13 +4,18 @@ on:
push:
branches: [main, master, core/*, desktop/*]
pull_request:
- branches-ignore: [wip/*, draft/*, temp/*, vue-nodes-migration]
+ branches-ignore:
+ [wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
+
+env:
+ DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
jobs:
setup:
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
+ sanitized-branch: ${{ steps.branch-info.outputs.sanitized }}
steps:
- name: Checkout ComfyUI
uses: actions/checkout@v4
@@ -36,6 +41,29 @@ jobs:
with:
node-version: lts/*
+ - name: Get current time
+ id: current-time
+ run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
+
+ - name: Comment PR - Tests Started
+ if: github.event_name == 'pull_request'
+ uses: edumserrano/find-create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body-includes: ''
+ comment-author: 'github-actions[bot]'
+ edit-mode: append
+ body: |
+
+
+ ---
+
+
+ [${{ steps.current-time.outputs.time }} UTC] Preparing browser tests across multiple browsers...
+
+ ---
+ *This comment will be updated when tests complete*
+
- name: Build ComfyUI_frontend
run: |
npm ci
@@ -46,6 +74,14 @@ jobs:
id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
+ - name: Generate sanitized branch name
+ id: branch-info
+ run: |
+ # Get branch name and sanitize it for Cloudflare branch names
+ BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
+ SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
+ echo "sanitized=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT
+
- name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
with:
@@ -57,6 +93,10 @@ jobs:
playwright-tests:
needs: setup
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ issues: write
+ contents: read
strategy:
fail-fast: false
matrix:
@@ -78,6 +118,32 @@ jobs:
with:
python-version: '3.10'
+ - name: Get current time
+ id: current-time
+ run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
+
+ - name: Set project name
+ id: project-name
+ run: |
+ if [ "${{ matrix.browser }}" = "chromium-0.5x" ]; then
+ echo "name=comfyui-playwright-chromium-0-5x" >> $GITHUB_OUTPUT
+ else
+ echo "name=comfyui-playwright-${{ matrix.browser }}" >> $GITHUB_OUTPUT
+ fi
+ echo "branch=${{ needs.setup.outputs.sanitized-branch }}" >> $GITHUB_OUTPUT
+
+ - name: Comment PR - Browser Test Started
+ if: github.event_name == 'pull_request'
+ uses: edumserrano/find-create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body-includes: ''
+ comment-author: 'github-actions[bot]'
+ edit-mode: append
+ body: |
+
+ ${{ matrix.browser }}: Running tests...
+
- name: Install requirements
run: |
python -m pip install --upgrade pip
@@ -96,13 +162,193 @@ jobs:
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
+ - name: Install Wrangler
+ run: npm install -g wrangler
+
- name: Run Playwright tests (${{ matrix.browser }})
- run: npx playwright test --project=${{ matrix.browser }}
+ id: playwright
+ run: npx playwright test --project=${{ matrix.browser }} --reporter=html
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
- if: always()
+ if: always() # note: use always() to allow results to be upload/report even tests failed.
with:
name: playwright-report-${{ matrix.browser }}
path: ComfyUI_frontend/playwright-report/
retention-days: 30
+
+ - name: Deploy to Cloudflare Pages (${{ matrix.browser }})
+ id: cloudflare-deploy
+ if: always()
+ run: |
+ # Retry logic for wrangler deploy (3 attempts)
+ RETRY_COUNT=0
+ MAX_RETRIES=3
+ SUCCESS=false
+
+ while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ $SUCCESS = false ]; do
+ RETRY_COUNT=$((RETRY_COUNT + 1))
+ echo "Deployment attempt $RETRY_COUNT of $MAX_RETRIES..."
+
+ if npx wrangler pages deploy ComfyUI_frontend/playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }}; then
+ SUCCESS=true
+ echo "Deployment successful on attempt $RETRY_COUNT"
+ else
+ echo "Deployment failed on attempt $RETRY_COUNT"
+ if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
+ echo "Retrying in 10 seconds..."
+ sleep 10
+ fi
+ fi
+ done
+
+ if [ $SUCCESS = false ]; then
+ echo "All deployment attempts failed"
+ exit 1
+ fi
+ env:
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+
+ - name: Save deployment info for summary
+ if: always()
+ run: |
+ mkdir -p deployment-info
+ # Use step conclusion to determine test result
+ if [ "${{ steps.playwright.conclusion }}" = "success" ]; then
+ EXIT_CODE="0"
+ else
+ EXIT_CODE="1"
+ fi
+ DEPLOYMENT_URL="${{ steps.cloudflare-deploy.outputs.deployment-url || steps.cloudflare-deploy.outputs.url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }}"
+ echo "${{ matrix.browser }}|${EXIT_CODE}|${DEPLOYMENT_URL}" > deployment-info/${{ matrix.browser }}.txt
+
+ - name: Upload deployment info
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: deployment-info-${{ matrix.browser }}
+ path: deployment-info/
+ retention-days: 1
+
+ - name: Get completion time
+ id: completion-time
+ if: always()
+ run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
+
+ - name: Comment PR - Browser Test Complete
+ if: always() && github.event_name == 'pull_request'
+ uses: edumserrano/find-create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body-includes: ''
+ comment-author: 'github-actions[bot]'
+ edit-mode: append
+ body: |
+ ${{ steps.playwright.conclusion == 'success' && '✅' || '❌' }} **${{ matrix.browser }}**: ${{ steps.playwright.conclusion == 'success' && 'Tests passed!' || 'Tests failed!' }} [View Report](${{ steps.cloudflare-deploy.outputs.deployment-url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }})
+
+ comment-summary:
+ needs: playwright-tests
+ runs-on: ubuntu-latest
+ if: always() && github.event_name == 'pull_request'
+ permissions:
+ pull-requests: write
+ steps:
+ - name: Download all deployment info
+ uses: actions/download-artifact@v4
+ with:
+ pattern: deployment-info-*
+ merge-multiple: true
+ path: deployment-info
+
+ - name: Get completion time
+ id: completion-time
+ run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
+
+ - name: Generate comment body
+ id: comment-body
+ run: |
+ echo "" > comment.md
+ echo "## 🎭 Playwright Test Results" >> comment.md
+ echo "" >> comment.md
+
+ # Check if all tests passed
+ ALL_PASSED=true
+ for file in deployment-info/*.txt; do
+ if [ -f "$file" ]; then
+ browser=$(basename "$file" .txt)
+ info=$(cat "$file")
+ exit_code=$(echo "$info" | cut -d'|' -f2)
+ if [ "$exit_code" != "0" ]; then
+ ALL_PASSED=false
+ break
+ fi
+ fi
+ done
+
+ if [ "$ALL_PASSED" = "true" ]; then
+ echo "✅ **All tests passed across all browsers!**" >> comment.md
+ else
+ echo "❌ **Some tests failed!**" >> comment.md
+ fi
+
+ echo "" >> comment.md
+ echo "⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md
+ echo "" >> comment.md
+ echo "### 📊 Test Reports by Browser" >> comment.md
+
+ for file in deployment-info/*.txt; do
+ if [ -f "$file" ]; then
+ browser=$(basename "$file" .txt)
+ info=$(cat "$file")
+ exit_code=$(echo "$info" | cut -d'|' -f2)
+ url=$(echo "$info" | cut -d'|' -f3)
+
+ if [ "$exit_code" = "0" ]; then
+ status="✅"
+ else
+ status="❌"
+ fi
+
+ echo "- $status **$browser**: [View Report]($url)" >> comment.md
+ fi
+ done
+
+ echo "" >> comment.md
+ echo "---" >> comment.md
+ if [ "$ALL_PASSED" = "true" ]; then
+ echo "🎉 Your tests are passing across all browsers!" >> comment.md
+ else
+ echo "⚠️ Please check the test reports for details on failures." >> comment.md
+ fi
+
+ - name: Comment PR - Tests Complete
+ uses: edumserrano/find-create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body-includes: ''
+ comment-author: 'github-actions[bot]'
+ edit-mode: replace
+ body-path: comment.md
+
+ - name: Check test results and fail if needed
+ run: |
+ # Check if all tests passed and fail the job if not
+ ALL_PASSED=true
+ for file in deployment-info/*.txt; do
+ if [ -f "$file" ]; then
+ info=$(cat "$file")
+ exit_code=$(echo "$info" | cut -d'|' -f2)
+ if [ "$exit_code" != "0" ]; then
+ ALL_PASSED=false
+ break
+ fi
+ fi
+ done
+
+ if [ "$ALL_PASSED" = "false" ]; then
+ echo "❌ Tests failed in one or more browsers. Failing the CI job."
+ exit 1
+ else
+ echo "✅ All tests passed across all browsers!"
+ fi
diff --git a/.gitignore b/.gitignore
index db5315411..8d19ceec5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,8 +13,9 @@ bun.lockb
pnpm-lock.yaml
yarn.lock
-# ESLint cache
+# Cache files
.eslintcache
+.prettiercache
node_modules
dist
@@ -22,6 +23,7 @@ dist-ssr
*.local
# Claude configuration
.claude/*.local.json
+.claude/settings.json
# Editor directories and files
.vscode/*
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..cccae51c9
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+src/types/comfyRegistryTypes.ts
+src/types/generatedManagerTypes.ts
\ No newline at end of file
diff --git a/.storybook/README.md b/.storybook/README.md
index f35753d27..902397471 100644
--- a/.storybook/README.md
+++ b/.storybook/README.md
@@ -93,6 +93,44 @@ export const WithVariant: Story = {
## Development Tips
+## ComfyUI Storybook Guidelines
+
+### Scope – When to Create Stories
+- **PrimeVue components**:
+ No need to create stories. Just refer to the official PrimeVue documentation.
+- **Custom shared components (design system components)**:
+ Always create stories. These components are built in collaboration with designers, and Storybook serves as both documentation and a communication tool.
+- **Container components (logic-heavy)**:
+ Do not create stories. Only the underlying pure UI components should be included in Storybook.
+
+### Maintenance Philosophy
+- Stories are lightweight and generally stable.
+ Once created, they rarely need updates unless:
+ - The design changes
+ - New props (e.g. size, color variants) are introduced
+- For existing usage patterns, simply copy real code examples into Storybook to create stories.
+
+### File Placement
+- Keep `*.stories.ts` files at the **same level as the component** (similar to test files).
+- This makes it easier to check usage examples without navigating to another directory.
+
+### Developer/Designer Workflow
+- **UI vs Container**: Separate pure UI components from container components.
+ Only UI components should live in Storybook.
+- **Communication Tool**: Storybook is not just about code quality—it enables designers and developers to see:
+ - Which props exist
+ - What cases are covered
+ - How variants (e.g. size, colors) look in isolation
+- **Example**:
+ `PackActionButton.vue` wraps a PrimeVue button with additional logic.
+ → Only create a story for the base UI button, not for the wrapper.
+
+### Suggested Workflow
+1. Use PrimeVue docs for standard components
+2. Use Storybook for **shared/custom components** that define our design system
+3. Keep story files alongside components
+4. When in doubt, focus on components reused across the app or those that need to be showcased to designers
+
### Best Practices
1. **Keep Stories Simple**: Each story should demonstrate one specific use case
@@ -135,6 +173,7 @@ export const WithLongText: Story = {
- **`main.ts`**: Core Storybook configuration and Vite integration
- **`preview.ts`**: Global decorators, parameters, and Vue app setup
- **`manager.ts`**: Storybook UI customization (if needed)
+- **`preview-head.html`**: Injects custom HTML into the `
` of every Storybook iframe (used for global styles, fonts, or fixes for iframe-specific issues)
## Chromatic Visual Testing
@@ -170,4 +209,22 @@ This Storybook setup includes:
- PrimeVue component library integration
- Proper alias resolution for `@/` imports
-For component-specific examples, see the NodePreview stories in `src/components/node/`.
\ No newline at end of file
+## Icon Usage in Storybook
+
+In this project, the `` syntax from unplugin-icons is not supported in Storybook.
+
+**Example:**
+
+```vue
+
+
+
+
+
+
+```
+
+This approach ensures icons render correctly in Storybook and remain consistent with the rest of the app.
+
diff --git a/browser_tests/README.md b/browser_tests/README.md
index 9c4d48f6b..ede6a303a 100644
--- a/browser_tests/README.md
+++ b/browser_tests/README.md
@@ -392,16 +392,6 @@ Option 2 - Generate local baselines for comparison:
npx playwright test --update-snapshots
```
-### Getting Test Artifacts from GitHub Actions
-
-When tests fail in CI, you can download screenshots and traces:
-
-1. Go to the failed workflow run in GitHub Actions
-2. Scroll to "Artifacts" section at the bottom
-3. Download `playwright-report` or `test-results`
-4. Extract and open the HTML report locally
-5. View actual vs expected screenshots and execution traces
-
### Creating New Screenshot Baselines
For PRs from `Comfy-Org/ComfyUI_frontend` branches:
@@ -412,6 +402,33 @@ For PRs from `Comfy-Org/ComfyUI_frontend` branches:
> **Note:** Fork PRs cannot auto-commit screenshots. A maintainer will need to commit the screenshots manually for you (don't worry, they'll do it).
+## Viewing Test Reports
+
+### Automated Test Deployment
+
+The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches.
+
+### Accessing Test Reports
+
+- **From PR comments**: Click the "View Report" links for each browser
+- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments)
+- **From GitHub Actions**: Download artifacts from failed runs
+
+### How It Works
+
+1. **Test execution**: All browser tests run in parallel across multiple browsers
+2. **Report generation**: HTML reports are generated for each browser configuration
+3. **Cloudflare deployment**: Each browser's report deploys to its own Cloudflare Pages project with branch isolation:
+ - `comfyui-playwright-chromium` (with branch-specific URLs)
+ - `comfyui-playwright-mobile-chrome` (with branch-specific URLs)
+ - `comfyui-playwright-chromium-2x` (2x scale, with branch-specific URLs)
+ - `comfyui-playwright-chromium-0-5x` (0.5x scale, with branch-specific URLs)
+
+4. **PR comments**: GitHub automatically updates PR comments with:
+ - ✅/❌ Test status for each browser
+ - Direct links to interactive test reports
+ - Real-time progress updates as tests complete
+
## Resources
- [Playwright UI Mode](https://playwright.dev/docs/test-ui-mode) - Interactive test debugging
diff --git a/package-lock.json b/package-lock.json
index 75311a1c8..5fe5dd683 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@comfyorg/comfyui-frontend",
- "version": "1.26.5",
+ "version": "1.26.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
- "version": "1.26.5",
+ "version": "1.26.6",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -84,6 +84,7 @@
"identity-obj-proxy": "^3.0.0",
"knip": "^5.62.0",
"lint-staged": "^15.2.7",
+ "lucide-vue-next": "^0.540.0",
"postcss": "^8.4.39",
"prettier": "^3.3.2",
"storybook": "^9.1.1",
@@ -12224,6 +12225,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-vue-next": {
+ "version": "0.540.0",
+ "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.540.0.tgz",
+ "integrity": "sha512-H7qhKVNKLyoFMo05pWcGSWBiLPiI3zJmWV65SuXWHlrIGIcvDer10xAyWcRJ0KLzIH5k5+yi7AGw/Xi1VF8Pbw==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "vue": ">=3.0.1"
+ }
+ },
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
diff --git a/package.json b/package.json
index 9627875af..45d95b84e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
- "version": "1.26.5",
+ "version": "1.26.6",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -14,8 +14,10 @@
"build:types": "vite build --config vite.types.config.mts && node scripts/prepare-types.js",
"zipdist": "node scripts/zipdist.js",
"typecheck": "vue-tsc --noEmit",
- "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
- "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
+ "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache",
+ "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache",
+ "format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
+ "format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
"test:browser": "npx playwright test",
"test:unit": "vitest run tests-ui/tests",
"test:component": "vitest run src/components/",
@@ -25,7 +27,8 @@
"lint:fix": "eslint src --cache --fix",
"lint:no-cache": "eslint src",
"lint:fix:no-cache": "eslint src --fix",
- "knip": "knip",
+ "knip": "knip --cache",
+ "knip:no-cache": "knip",
"locale": "lobe-i18n locale",
"collect-i18n": "playwright test --config=playwright.i18n.config.ts",
"json-schema": "tsx scripts/generate-json-schema.ts",
@@ -81,7 +84,8 @@
"vitest": "^2.0.0",
"vue-tsc": "^2.1.10",
"zip-dir": "^2.0.0",
- "zod-to-json-schema": "^3.24.1"
+ "zod-to-json-schema": "^3.24.1",
+ "lucide-vue-next": "^0.540.0"
},
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
diff --git a/src/assets/icons/custom/workflow.svg b/src/assets/icons/custom/workflow.svg
index 72f90c1a4..043d24e7b 100644
--- a/src/assets/icons/custom/workflow.svg
+++ b/src/assets/icons/custom/workflow.svg
@@ -1,7 +1,3 @@
-