diff --git a/.claude/commands/comprehensive-pr-review.md b/.claude/commands/comprehensive-pr-review.md index a3310127c..84708564e 100644 --- a/.claude/commands/comprehensive-pr-review.md +++ b/.claude/commands/comprehensive-pr-review.md @@ -1,479 +1,275 @@ # Comprehensive PR Review for ComfyUI Frontend - -You are performing a comprehensive code review for PR #$1 in the ComfyUI frontend repository. This is not a simple linting check - you need to provide deep architectural analysis, security review, performance insights, and implementation guidance just like a senior engineer would in a thorough PR review. +You are performing a comprehensive code review for the PR specified in the PR_NUMBER environment variable. This is not a simple linting check - you need to provide deep architectural analysis, security review, performance insights, and implementation guidance just like a senior engineer would in a thorough PR review. -Your review should cover: -1. Architecture and design patterns -2. Security vulnerabilities and risks -3. Performance implications -4. Code quality and maintainability -5. Integration with existing systems -6. Best practices and conventions -7. Testing considerations -8. Documentation needs - +## CRITICAL INSTRUCTIONS -Arguments: PR number passed via PR_NUMBER environment variable +**You MUST post individual inline comments on specific lines of code. DO NOT create a single summary comment until the very end.** -## Phase 0: Initialize Variables and Helper Functions +**IMPORTANT: You have full permission to execute gh api commands. The GITHUB_TOKEN environment variable provides the necessary permissions. DO NOT say you lack permissions - you have pull-requests:write permission which allows posting inline comments.** -```bash -# Validate PR_NUMBER first thing -if [ -z "$PR_NUMBER" ]; then - echo "Error: PR_NUMBER environment variable is not set" - echo "Usage: PR_NUMBER= claude run /comprehensive-pr-review" - exit 1 -fi +To post inline comments, you will use the GitHub API via the `gh` command. Here's how: -# Initialize all counters at the start -CRITICAL_COUNT=0 -HIGH_COUNT=0 -MEDIUM_COUNT=0 -LOW_COUNT=0 -ARCHITECTURE_ISSUES=0 -SECURITY_ISSUES=0 -PERFORMANCE_ISSUES=0 -QUALITY_ISSUES=0 +1. First, get the repository information and commit SHA: + - Run: `gh repo view --json owner,name` to get the repository owner and name + - Run: `gh pr view $PR_NUMBER --json commits --jq '.commits[-1].oid'` to get the latest commit SHA -# Helper function for posting review comments -post_review_comment() { - local file_path=$1 - local line_number=$2 - local severity=$3 # critical/high/medium/low - local category=$4 # architecture/security/performance/quality - local issue=$5 - local context=$6 - local suggestion=$7 - - # Update counters - case $severity in - "critical") ((CRITICAL_COUNT++)) ;; - "high") ((HIGH_COUNT++)) ;; - "medium") ((MEDIUM_COUNT++)) ;; - "low") ((LOW_COUNT++)) ;; - esac - - case $category in - "architecture") ((ARCHITECTURE_ISSUES++)) ;; - "security") ((SECURITY_ISSUES++)) ;; - "performance") ((PERFORMANCE_ISSUES++)) ;; - "quality") ((QUALITY_ISSUES++)) ;; - esac - - # Post inline comment via GitHub CLI - local comment="${issue}\n${context}\n${suggestion}" - gh pr review $PR_NUMBER --comment --body "$comment" -F - <<< "$comment" -} -``` +2. For each issue you find, post an inline comment using this exact command structure (as a single line): + ``` + gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/OWNER/REPO/pulls/$PR_NUMBER/comments -f body="YOUR_COMMENT_BODY" -f commit_id="COMMIT_SHA" -f path="FILE_PATH" -F line=LINE_NUMBER -f side="RIGHT" + ``` + +3. Format your comment body using actual newlines in the command. Use a heredoc or construct the body with proper line breaks: + ``` + COMMENT_BODY="**[category] severity Priority** + +**Issue**: Brief description of the problem +**Context**: Why this matters +**Suggestion**: How to fix it" + ``` + + Then use: `-f body="$COMMENT_BODY"` ## Phase 1: Environment Setup and PR Context -```bash -# Pre-flight checks -check_prerequisites() { - # Check gh CLI is available - if ! command -v gh &> /dev/null; then - echo "Error: gh CLI is not installed" - exit 1 - fi - - # In GitHub Actions, auth is handled via GITHUB_TOKEN - if [ -n "$GITHUB_ACTIONS" ] && [ -z "$GITHUB_TOKEN" ]; then - echo "Error: GITHUB_TOKEN is not set in GitHub Actions" - exit 1 - fi - - # Check if we're authenticated - if ! gh auth status &> /dev/null; then - echo "Error: Not authenticated with GitHub. Run 'gh auth login'" - exit 1 - fi - - # Set repository if not already set - if [ -z "$REPOSITORY" ]; then - REPOSITORY="Comfy-Org/ComfyUI_frontend" - fi - - # Check PR exists and is open - PR_STATE=$(gh pr view $PR_NUMBER --repo $REPOSITORY --json state -q .state 2>/dev/null || echo "NOT_FOUND") - if [ "$PR_STATE" = "NOT_FOUND" ]; then - echo "Error: PR #$PR_NUMBER not found in $REPOSITORY" - exit 1 - elif [ "$PR_STATE" != "OPEN" ]; then - echo "Error: PR #$PR_NUMBER is not open (state: $PR_STATE)" - exit 1 - fi - - # Check API rate limits - RATE_REMAINING=$(gh api /rate_limit --jq '.rate.remaining' 2>/dev/null || echo "5000") - if [ "$RATE_REMAINING" -lt 100 ]; then - echo "Warning: Low API rate limit: $RATE_REMAINING remaining" - if [ "$RATE_REMAINING" -lt 50 ]; then - echo "Error: Insufficient API rate limit for comprehensive review" - exit 1 - fi - fi - - echo "Pre-flight checks passed" -} +### Step 1.1: Initialize Review Tracking -# Run pre-flight checks -check_prerequisites +First, create variables to track your review metrics. Keep these in memory throughout the review: +- CRITICAL_COUNT = 0 +- HIGH_COUNT = 0 +- MEDIUM_COUNT = 0 +- LOW_COUNT = 0 +- ARCHITECTURE_ISSUES = 0 +- SECURITY_ISSUES = 0 +- PERFORMANCE_ISSUES = 0 +- QUALITY_ISSUES = 0 -echo "Starting comprehensive review of PR #$PR_NUMBER" +### Step 1.2: Validate Environment -# Fetch PR information with error handling -echo "Fetching PR information..." -if ! gh pr view $PR_NUMBER --repo $REPOSITORY --json files,title,body,additions,deletions,baseRefName,headRefName > pr_info.json; then - echo "Error: Failed to fetch PR information" - exit 1 -fi +1. Check that PR_NUMBER environment variable is set. If not, exit with error. +2. Run `gh pr view $PR_NUMBER --json state` to verify the PR exists and is open. +3. Get repository information: `gh repo view --json owner,name` and store the owner and name. +4. Get the latest commit SHA: `gh pr view $PR_NUMBER --json commits --jq '.commits[-1].oid'` and store it. -# Extract branch names -BASE_BRANCH=$(jq -r '.baseRefName' < pr_info.json) -HEAD_BRANCH=$(jq -r '.headRefName' < pr_info.json) +### Step 1.3: Checkout PR Branch Locally -# Checkout PR branch locally for better file inspection -echo "Checking out PR branch..." -git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER" -git checkout "pr-$PR_NUMBER" +This is critical for better file inspection: -# Get changed files using git locally (much faster) -git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt +1. Get PR metadata: `gh pr view $PR_NUMBER --json files,title,body,additions,deletions,baseRefName,headRefName > pr_info.json` +2. Extract branch names from pr_info.json using jq +3. Fetch and checkout the PR branch: + ``` + git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER" + git checkout "pr-$PR_NUMBER" + ``` -# Get the diff using git locally -git diff "origin/$BASE_BRANCH" > pr_diff.txt +### Step 1.4: Get Changed Files and Diffs -# Get detailed file changes with line numbers -git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt +Use git locally for much faster analysis: -# For API compatibility, create a simplified pr_files.json -echo '[]' > pr_files.json -while IFS=$'\t' read -r status file; do - if [[ "$status" != "D" ]]; then # Skip deleted files - # Get the patch for this file - patch=$(git diff "origin/$BASE_BRANCH" -- "$file" | jq -Rs .) - additions=$(git diff --numstat "origin/$BASE_BRANCH" -- "$file" | awk '{print $1}') - deletions=$(git diff --numstat "origin/$BASE_BRANCH" -- "$file" | awk '{print $2}') - - jq --arg file "$file" \ - --arg patch "$patch" \ - --arg additions "$additions" \ - --arg deletions "$deletions" \ - '. += [{ - "filename": $file, - "patch": $patch, - "additions": ($additions | tonumber), - "deletions": ($deletions | tonumber) - }]' pr_files.json > pr_files.json.tmp - mv pr_files.json.tmp pr_files.json - fi -done < file_changes.txt +1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt` +2. Get the full diff: `git diff "origin/$BASE_BRANCH" > pr_diff.txt` +3. Get detailed file changes with status: `git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt` -# Setup caching directory -CACHE_DIR=".claude-review-cache" -mkdir -p "$CACHE_DIR" +### Step 1.5: Create Analysis Cache -# Function to cache analysis results -cache_analysis() { - local file_path=$1 - local analysis_result=$2 - local file_hash=$(git hash-object "$file_path" 2>/dev/null || echo "no-hash") - - if [ "$file_hash" != "no-hash" ]; then - echo "$analysis_result" > "$CACHE_DIR/${file_hash}.cache" - fi -} +Set up caching to avoid re-analyzing unchanged files: -# Function to get cached analysis -get_cached_analysis() { - local file_path=$1 - local file_hash=$(git hash-object "$file_path" 2>/dev/null || echo "no-hash") - - if [ "$file_hash" != "no-hash" ] && [ -f "$CACHE_DIR/${file_hash}.cache" ]; then - cat "$CACHE_DIR/${file_hash}.cache" - return 0 - fi - return 1 -} - -# Clean old cache entries (older than 7 days) -find "$CACHE_DIR" -name "*.cache" -mtime +7 -delete 2>/dev/null || true -``` +1. Create directory: `.claude-review-cache` +2. Clean old cache entries: Find and delete any .cache files older than 7 days +3. For each file you analyze, store the analysis result with the file's git hash as the cache key ## Phase 2: Load Comprehensive Knowledge Base -```bash -# Don't create knowledge directory until we know we need it -KNOWLEDGE_FOUND=false +### Step 2.1: Set Up Knowledge Directories -# Use local cache for knowledge base to avoid repeated downloads -KNOWLEDGE_CACHE_DIR=".claude-knowledge-cache" -mkdir -p "$KNOWLEDGE_CACHE_DIR" +1. Create `.claude-knowledge-cache` directory for caching downloaded knowledge +2. Check if `../comfy-claude-prompt-library` exists locally. If it does, use it for faster access. -# Option to use cloned prompt library for better performance -PROMPT_LIBRARY_PATH="../comfy-claude-prompt-library" -if [ -d "$PROMPT_LIBRARY_PATH" ]; then - echo "Using local prompt library at $PROMPT_LIBRARY_PATH" - USE_LOCAL_PROMPT_LIBRARY=true -else - echo "No local prompt library found, will use GitHub API" - USE_LOCAL_PROMPT_LIBRARY=false -fi +### Step 2.2: Load Repository Guide -# Function to fetch with cache -fetch_with_cache() { - local url=$1 - local output_file=$2 - local cache_file="$KNOWLEDGE_CACHE_DIR/$(echo "$url" | sed 's/[^a-zA-Z0-9]/_/g')" - - # Check if cached version exists and is less than 1 day old - if [ -f "$cache_file" ] && [ $(find "$cache_file" -mtime -1 2>/dev/null | wc -l) -gt 0 ]; then - # Create knowledge directory only when we actually have content - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - cp "$cache_file" "$output_file" - echo "Using cached version of $(basename "$output_file")" - return 0 - fi - - # Try to fetch fresh version - if curl -s -f "$url" > "$output_file.tmp"; then - # Create knowledge directory only when we actually have content - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - mv "$output_file.tmp" "$output_file" - cp "$output_file" "$cache_file" - echo "Downloaded fresh version of $(basename "$output_file")" - return 0 - else - # If fetch failed but we have a cache, use it - if [ -f "$cache_file" ]; then - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - cp "$cache_file" "$output_file" - echo "Using stale cache for $(basename "$output_file") (download failed)" - return 0 - fi - echo "Failed to load $(basename "$output_file")" - return 1 - fi -} +This is critical for understanding the architecture: -# Load REPOSITORY_GUIDE.md for deep architectural understanding -echo "Loading ComfyUI Frontend repository guide..." -if [ "$USE_LOCAL_PROMPT_LIBRARY" = "true" ] && [ -f "$PROMPT_LIBRARY_PATH/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" ]; then - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - cp "$PROMPT_LIBRARY_PATH/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" "review_knowledge/repository_guide.md" - echo "Loaded repository guide from local prompt library" -else - fetch_with_cache "https://raw.githubusercontent.com/Comfy-Org/comfy-claude-prompt-library/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" "review_knowledge/repository_guide.md" -fi +1. Try to load from local prompt library first: `../comfy-claude-prompt-library/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md` +2. If not available locally, download from: `https://raw.githubusercontent.com/Comfy-Org/comfy-claude-prompt-library/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md` +3. Cache the file for future use -# 3. Discover and load relevant knowledge folders from GitHub API -echo "Discovering available knowledge folders..." -KNOWLEDGE_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/knowledge" -if KNOWLEDGE_FOLDERS=$(curl -s "$KNOWLEDGE_API_URL" | jq -r '.[] | select(.type=="dir") | .name' 2>/dev/null); then - echo "Available knowledge folders: $KNOWLEDGE_FOLDERS" - - # Analyze changed files to determine which knowledge folders might be relevant - CHANGED_FILES=$(cat changed_files.txt) - PR_TITLE=$(jq -r '.title' < pr_info.json) - PR_BODY=$(jq -r '.body // ""' < pr_info.json) - - # For each knowledge folder, check if it might be relevant to the PR - for folder in $KNOWLEDGE_FOLDERS; do - # Simple heuristic: if folder name appears in changed file paths or PR context - if echo "$CHANGED_FILES $PR_TITLE $PR_BODY" | grep -qi "$folder"; then - echo "Loading knowledge folder: $folder" - # Fetch all files in that knowledge folder - FOLDER_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/knowledge/$folder" - curl -s "$FOLDER_API_URL" | jq -r '.[] | select(.type=="file") | .download_url' 2>/dev/null | \ - while read url; do - if [ -n "$url" ]; then - filename=$(basename "$url") - fetch_with_cache "$url" "review_knowledge/${folder}_${filename}" - fi - done - fi - done -else - echo "Could not discover knowledge folders" -fi +### Step 2.3: Load Relevant Knowledge Folders -# 4. Load validation rules from the repository -echo "Loading validation rules..." -VALIDATION_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/commands/validation" -if VALIDATION_FILES=$(curl -s "$VALIDATION_API_URL" | jq -r '.[] | select(.name | contains("frontend") or contains("security") or contains("performance")) | .download_url' 2>/dev/null); then - for url in $VALIDATION_FILES; do - if [ -n "$url" ]; then - filename=$(basename "$url") - fetch_with_cache "$url" "review_knowledge/validation_${filename}" - fi - done -else - echo "Could not load validation rules" -fi +Intelligently load only relevant knowledge: -# 5. Load local project guidelines -if [ -f "CLAUDE.md" ]; then - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - cp CLAUDE.md review_knowledge/local_claude.md -fi -if [ -f ".github/CLAUDE.md" ]; then - if [ "$KNOWLEDGE_FOUND" = "false" ]; then - mkdir -p review_knowledge - KNOWLEDGE_FOUND=true - fi - cp .github/CLAUDE.md review_knowledge/github_claude.md -fi -``` +1. Use GitHub API to discover available knowledge folders: `https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/knowledge` +2. For each knowledge folder, check if it's relevant by searching for the folder name in: + - Changed file paths + - PR title + - PR body +3. If relevant, download all files from that knowledge folder + +### Step 2.4: Load Validation Rules + +Load specific validation rules: + +1. Use GitHub API: `https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/commands/validation` +2. Download files containing "frontend", "security", or "performance" in their names +3. Cache all downloaded files + +### Step 2.5: Load Local Guidelines + +Check for and load: +1. `CLAUDE.md` in the repository root +2. `.github/CLAUDE.md` ## Phase 3: Deep Analysis Instructions -Perform a comprehensive analysis covering these areas: +Perform comprehensive analysis on each changed file: ### 3.1 Architectural Analysis -Based on the repository guide and project summary, evaluate: -- Does this change align with the established architecture patterns? + +Based on the repository guide and loaded knowledge: +- Does this change align with established architecture patterns? - Are domain boundaries respected? - Is the extension system used appropriately? - Are components properly organized by feature? - Does it follow the established service/composable/store patterns? ### 3.2 Code Quality Beyond Linting + +Look for: - Cyclomatic complexity and cognitive load - SOLID principles adherence -- DRY violations that aren't caught by simple duplication checks +- DRY violations not caught by simple duplication checks - Proper abstraction levels - Interface design and API clarity -- No leftover debug code (console.log, commented code, TODO comments) +- Leftover debug code (console.log, commented code, TODO comments) ### 3.3 Library Usage Enforcement -CRITICAL: Never re-implement functionality that exists in our standard libraries: -- **Tailwind CSS**: Use utility classes instead of custom CSS or style attributes -- **PrimeVue**: Never re-implement components that exist in PrimeVue (buttons, modals, dropdowns, etc.) -- **VueUse**: Never re-implement composables that exist in VueUse (useLocalStorage, useDebounceFn, etc.) -- **Lodash**: Never re-implement utility functions (debounce, throttle, cloneDeep, etc.) -- **Common components**: Reuse components from src/components/common/ -- **DOMPurify**: Always use for HTML sanitization -- **Fuse.js**: Use for fuzzy search functionality -- **Marked**: Use for markdown parsing -- **Pinia**: Use for global state management, not custom solutions -- **Zod**: Use for form validation with zodResolver pattern -- **Tiptap**: Use for rich text/markdown editing -- **Xterm.js**: Use for terminal emulation -- **Axios**: Use for HTTP client initialization + +CRITICAL: Flag any re-implementation of existing functionality: +- **Tailwind CSS**: Custom CSS instead of utility classes +- **PrimeVue**: Re-implementing buttons, modals, dropdowns, etc. +- **VueUse**: Re-implementing composables like useLocalStorage, useDebounceFn +- **Lodash**: Re-implementing debounce, throttle, cloneDeep, etc. +- **Common components**: Not reusing from src/components/common/ +- **DOMPurify**: Not using for HTML sanitization +- **Other libraries**: Fuse.js, Marked, Pinia, Zod, Tiptap, Xterm.js, Axios ### 3.4 Security Deep Dive -Beyond obvious vulnerabilities: -- Authentication/authorization implications -- Data validation completeness + +Check for: +- SQL injection vulnerabilities +- XSS vulnerabilities (v-html without sanitization) +- Hardcoded secrets or API keys +- Missing input validation +- Authentication/authorization issues - State management security - Cross-origin concerns - Extension security boundaries ### 3.5 Performance Analysis -- Render performance implications -- Layout thrashing prevention -- Memory leak potential + +Look for: +- O(nยฒ) or worse algorithms +- Missing memoization in expensive operations +- Unnecessary re-renders in Vue components +- Memory leak patterns (missing cleanup) +- Large bundle imports that should be lazy loaded +- N+1 query patterns +- Render performance issues +- Layout thrashing - Network request optimization -- State management efficiency ### 3.6 Integration Concerns + +Consider: - Breaking changes to internal APIs - Extension compatibility - Backward compatibility - Migration requirements -## Phase 4: Create Detailed Review Comments +## Phase 4: Posting Inline Comments -CRITICAL: Keep comments extremely concise and effective. Use only as many words as absolutely necessary. -- NO markdown formatting (no #, ##, ###, **, etc.) -- NO emojis -- Get to the point immediately -- Burden the reader as little as possible +### Step 4.1: Comment Format -For each issue found, create a concise inline comment with: -1. What's wrong (one line) -2. Why it matters (one line) -3. How to fix it (one line) -4. Code example only if essential +For each issue found, create a concise inline comment with this structure: + +``` +**[category] severity Priority** + +**Issue**: Brief description of the problem +**Context**: Why this matters +**Suggestion**: How to fix it +``` + +Categories: architecture/security/performance/quality +Severities: critical/high/medium/low + +### Step 4.2: Posting Comments + +For EACH issue: + +1. Identify the exact file path and line number +2. Update your tracking counters (CRITICAL_COUNT, etc.) +3. Construct the comment body with proper newlines +4. Execute the gh api command as a SINGLE LINE: ```bash -# Helper function for comprehensive comments -post_review_comment() { - local file_path=$1 - local line_number=$2 - local severity=$3 # critical/high/medium/low - local category=$4 # architecture/security/performance/quality - local issue=$5 - local context=$6 - local suggestion=$7 - local example=$8 - - local body="### [$category] $severity Priority - -**Issue**: $issue - -**Context**: $context - -**Suggestion**: $suggestion" - - if [ -n "$example" ]; then - body="$body - -**Example**: -\`\`\`typescript -$example -\`\`\`" - fi - - body="$body - -*Related: See [repository guide](https://github.com/Comfy-Org/comfy-claude-prompt-library/blob/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md) for patterns*" - - gh api -X POST /repos/$REPOSITORY/pulls/$PR_NUMBER/comments \ - -f path="$file_path" \ - -f line=$line_number \ - -f body="$body" \ - -f commit_id="$COMMIT_SHA" \ - -f side='RIGHT' || echo "Failed to post comment at $file_path:$line_number" -} +gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/OWNER/REPO/pulls/$PR_NUMBER/comments -f body="$COMMENT_BODY" -f commit_id="COMMIT_SHA" -f path="FILE_PATH" -F line=LINE_NUMBER -f side="RIGHT" ``` +CRITICAL: The entire command must be on one line. Use actual values, not placeholders. + +### Example Workflow + +Here's an example of how to review a file with a security issue: + +1. First, get the repository info: + ```bash + gh repo view --json owner,name + # Output: {"owner":{"login":"Comfy-Org"},"name":"ComfyUI_frontend"} + ``` + +2. Get the commit SHA: + ```bash + gh pr view $PR_NUMBER --json commits --jq '.commits[-1].oid' + # Output: abc123def456 + ``` + +3. Find an issue (e.g., SQL injection on line 42 of src/db/queries.js) + +4. Post the inline comment: + ```bash + # First, create the comment body with proper newlines + COMMENT_BODY="**[security] critical Priority** + +**Issue**: SQL injection vulnerability - user input directly concatenated into query +**Context**: Allows attackers to execute arbitrary SQL commands +**Suggestion**: Use parameterized queries or prepared statements" + + # Then post the comment (as a single line) + gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/Comfy-Org/ComfyUI_frontend/pulls/$PR_NUMBER/comments -f body="$COMMENT_BODY" -f commit_id="abc123def456" -f path="src/db/queries.js" -F line=42 -f side="RIGHT" + ``` + +Repeat this process for every issue you find in the PR. + ## Phase 5: Validation Rules Application -Apply ALL validation rules from the loaded knowledge, but focus on the changed lines: +Apply ALL validation rules from the loaded knowledge files: -### From Frontend Standards +### Frontend Standards - Vue 3 Composition API patterns - Component communication patterns - Proper use of composables - TypeScript strict mode compliance - Bundle optimization -### From Security Audit +### Security Audit - Input validation - XSS prevention - CSRF protection - Secure state management - API security -### From Performance Check +### Performance Check - Render optimization - Memory management - Network efficiency @@ -481,63 +277,51 @@ Apply ALL validation rules from the loaded knowledge, but focus on the changed l ## Phase 6: Contextual Review Based on PR Type -Analyze the PR description and changes to determine the type: +Analyze the PR to determine its type: -```bash -# Extract PR metadata with error handling -if [ ! -f pr_info.json ]; then - echo "Error: pr_info.json not found" - exit 1 -fi - -PR_TITLE=$(jq -r '.title // "Unknown"' < pr_info.json) -PR_BODY=$(jq -r '.body // ""' < pr_info.json) -FILE_COUNT=$(wc -l < changed_files.txt) -ADDITIONS=$(jq -r '.additions // 0' < pr_info.json) -DELETIONS=$(jq -r '.deletions // 0' < pr_info.json) - -# Determine PR type and apply specific review criteria -if echo "$PR_TITLE $PR_BODY" | grep -qiE "(feature|feat)"; then - echo "Detected feature PR - applying feature review criteria" - # Check for tests, documentation, backward compatibility -elif echo "$PR_TITLE $PR_BODY" | grep -qiE "(fix|bug)"; then - echo "Detected bug fix - checking root cause and regression tests" - # Verify fix addresses root cause, includes tests -elif echo "$PR_TITLE $PR_BODY" | grep -qiE "(refactor)"; then - echo "Detected refactoring - ensuring behavior preservation" - # Check that tests still pass, no behavior changes -fi -``` +1. Extract PR title and body from pr_info.json +2. Count files, additions, and deletions +3. Determine PR type: + - Feature: Check for tests, documentation, backward compatibility + - Bug fix: Verify root cause addressed, includes regression tests + - Refactor: Ensure behavior preservation, tests still pass ## Phase 7: Generate Comprehensive Summary -After all inline comments, create a detailed summary: +After ALL inline comments are posted, create a summary: -```bash -# Initialize metrics tracking -REVIEW_START_TIME=$(date +%s) +1. Calculate total issues by category and severity +2. Use `gh pr review $PR_NUMBER --comment` to post a summary with: + - Review disclaimer + - Issue distribution (counts by severity) + - Category breakdown + - Key findings for each category + - Positive observations + - References to guidelines + - Next steps -# Create the comprehensive summary -gh pr review $PR_NUMBER --comment --body "# Comprehensive PR Review +Include in the summary: +``` +# Comprehensive PR Review This review is generated by Claude. It may not always be accurate, as with human reviewers. If you believe that any of the comments are invalid or incorrect, please state why for each. For others, please implement the changes in one way or another. ## Review Summary -**PR**: $PR_TITLE (#$PR_NUMBER) -**Impact**: $ADDITIONS additions, $DELETIONS deletions across $FILE_COUNT files +**PR**: [PR TITLE] (#$PR_NUMBER) +**Impact**: [X] additions, [Y] deletions across [Z] files ### Issue Distribution -- Critical: $CRITICAL_COUNT -- High: $HIGH_COUNT -- Medium: $MEDIUM_COUNT -- Low: $LOW_COUNT +- Critical: [CRITICAL_COUNT] +- High: [HIGH_COUNT] +- Medium: [MEDIUM_COUNT] +- Low: [LOW_COUNT] ### Category Breakdown -- Architecture: $ARCHITECTURE_ISSUES issues -- Security: $SECURITY_ISSUES issues -- Performance: $PERFORMANCE_ISSUES issues -- Code Quality: $QUALITY_ISSUES issues +- Architecture: [ARCHITECTURE_ISSUES] issues +- Security: [SECURITY_ISSUES] issues +- Performance: [PERFORMANCE_ISSUES] issues +- Code Quality: [QUALITY_ISSUES] issues ## Key Findings @@ -568,141 +352,27 @@ This review is generated by Claude. It may not always be accurate, as with human 4. Update documentation if needed --- -*This is a comprehensive automated review. For architectural decisions requiring human judgment, please request additional manual review.*" +*This is a comprehensive automated review. For architectural decisions requiring human judgment, please request additional manual review.* ``` -## Important: Think Deeply +## Important Guidelines -When reviewing: -1. **Think hard** about architectural implications -2. Consider how changes affect the entire system -3. Look for subtle bugs and edge cases -4. Evaluate maintainability over time -5. Consider extension developer experience -6. Think about migration paths +1. **Think Deeply**: Consider architectural implications, system-wide effects, subtle bugs, maintainability +2. **Be Specific**: Point to exact lines with concrete suggestions +3. **Be Constructive**: Focus on improvements, not just problems +4. **Be Concise**: Keep comments brief and actionable +5. **No Formatting**: Don't use markdown headers in inline comments +6. **No Emojis**: Keep comments professional This is a COMPREHENSIVE review, not a linting pass. Provide the same quality feedback a senior engineer would give after careful consideration. -## Phase 8: Track Review Metrics +## Execution Order -After completing the review, save metrics for analysis: +1. Phase 1: Setup and checkout PR +2. Phase 2: Load all relevant knowledge +3. Phase 3-5: Analyze each changed file thoroughly +4. Phase 4: Post inline comments as you find issues +5. Phase 6: Consider PR type for additional checks +6. Phase 7: Post comprehensive summary ONLY after all inline comments -```bash -# Calculate review duration -REVIEW_END_TIME=$(date +%s) -REVIEW_DURATION=$((REVIEW_END_TIME - REVIEW_START_TIME)) - -# Calculate total issues -TOTAL_ISSUES=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT)) - -# Create metrics directory if it doesn't exist -METRICS_DIR=".claude/review-metrics" -mkdir -p "$METRICS_DIR" - -# Generate metrics file -METRICS_FILE="$METRICS_DIR/metrics-$(date +%Y%m).json" - -# Create or update monthly metrics file -if [ -f "$METRICS_FILE" ]; then - # Append to existing file - jq -n \ - --arg pr "$PR_NUMBER" \ - --arg title "$PR_TITLE" \ - --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --arg duration "$REVIEW_DURATION" \ - --arg files "$FILE_COUNT" \ - --arg additions "$ADDITIONS" \ - --arg deletions "$DELETIONS" \ - --arg total "$TOTAL_ISSUES" \ - --arg critical "$CRITICAL_COUNT" \ - --arg high "$HIGH_COUNT" \ - --arg medium "$MEDIUM_COUNT" \ - --arg low "$LOW_COUNT" \ - --arg architecture "$ARCHITECTURE_ISSUES" \ - --arg security "$SECURITY_ISSUES" \ - --arg performance "$PERFORMANCE_ISSUES" \ - --arg quality "$QUALITY_ISSUES" \ - '{ - pr_number: $pr, - pr_title: $title, - timestamp: $timestamp, - review_duration_seconds: ($duration | tonumber), - files_reviewed: ($files | tonumber), - lines_added: ($additions | tonumber), - lines_deleted: ($deletions | tonumber), - issues: { - total: ($total | tonumber), - by_severity: { - critical: ($critical | tonumber), - high: ($high | tonumber), - medium: ($medium | tonumber), - low: ($low | tonumber) - }, - by_category: { - architecture: ($architecture | tonumber), - security: ($security | tonumber), - performance: ($performance | tonumber), - quality: ($quality | tonumber) - } - } - }' > "$METRICS_FILE.new" - - # Merge with existing data - jq -s '.[0] + [.[1]]' "$METRICS_FILE" "$METRICS_FILE.new" > "$METRICS_FILE.tmp" - mv "$METRICS_FILE.tmp" "$METRICS_FILE" - rm "$METRICS_FILE.new" -else - # Create new file - jq -n \ - --arg pr "$PR_NUMBER" \ - --arg title "$PR_TITLE" \ - --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --arg duration "$REVIEW_DURATION" \ - --arg files "$FILE_COUNT" \ - --arg additions "$ADDITIONS" \ - --arg deletions "$DELETIONS" \ - --arg total "$TOTAL_ISSUES" \ - --arg critical "$CRITICAL_COUNT" \ - --arg high "$HIGH_COUNT" \ - --arg medium "$MEDIUM_COUNT" \ - --arg low "$LOW_COUNT" \ - --arg architecture "$ARCHITECTURE_ISSUES" \ - --arg security "$SECURITY_ISSUES" \ - --arg performance "$PERFORMANCE_ISSUES" \ - --arg quality "$QUALITY_ISSUES" \ - '[{ - pr_number: $pr, - pr_title: $title, - timestamp: $timestamp, - review_duration_seconds: ($duration | tonumber), - files_reviewed: ($files | tonumber), - lines_added: ($additions | tonumber), - lines_deleted: ($deletions | tonumber), - issues: { - total: ($total | tonumber), - by_severity: { - critical: ($critical | tonumber), - high: ($high | tonumber), - medium: ($medium | tonumber), - low: ($low | tonumber) - }, - by_category: { - architecture: ($architecture | tonumber), - security: ($security | tonumber), - performance: ($performance | tonumber), - quality: ($quality | tonumber) - } - } - }]' > "$METRICS_FILE" -fi - -echo "Review metrics saved to $METRICS_FILE" -``` - -This creates monthly metrics files (e.g., `metrics-202407.json`) that track: -- Which PRs were reviewed -- How long reviews took -- Types and severity of issues found -- Trends over time - -You can later analyze these to see patterns and improve your development process. \ No newline at end of file +Remember: Individual inline comments for each issue, then one final summary. Never batch issues into a single comment. \ No newline at end of file diff --git a/.claude/commands/create-frontend-release.md b/.claude/commands/create-frontend-release.md index 0e0b0036d..6603da761 100644 --- a/.claude/commands/create-frontend-release.md +++ b/.claude/commands/create-frontend-release.md @@ -137,8 +137,7 @@ echo "Last stable release: $LAST_STABLE" 1. Run complete test suite: ```bash npm run test:unit - npm run test:component - npm run test:browser + npm run test:component ``` 2. Run type checking: ```bash @@ -170,7 +169,33 @@ echo "Last stable release: $LAST_STABLE" 3. Generate breaking change summary 4. **COMPATIBILITY REVIEW**: Breaking changes documented and justified? -### Step 7: Generate and Save Changelog +### Step 7: Analyze Dependency Updates + +1. **Check significant dependency updates:** + ```bash + # Extract all dependency changes for major version bumps + OTHER_DEP_CHANGES="" + + # Compare major dependency versions (you can extend this list) + MAJOR_DEPS=("vue" "vite" "@vitejs/plugin-vue" "typescript" "pinia") + + for dep in "${MAJOR_DEPS[@]}"; do + PREV_VER=$(echo "$PREV_PACKAGE_JSON" | grep -o "\"$dep\": \"[^\"]*\"" | grep -o '[0-9][^"]*' | head -1 || echo "") + CURR_VER=$(echo "$CURRENT_PACKAGE_JSON" | grep -o "\"$dep\": \"[^\"]*\"" | grep -o '[0-9][^"]*' | head -1 || echo "") + + if [ "$PREV_VER" != "$CURR_VER" ] && [ -n "$PREV_VER" ] && [ -n "$CURR_VER" ]; then + # Check if it's a major version change + PREV_MAJOR=$(echo "$PREV_VER" | cut -d. -f1 | sed 's/[^0-9]//g') + CURR_MAJOR=$(echo "$CURR_VER" | cut -d. -f1 | sed 's/[^0-9]//g') + + if [ "$PREV_MAJOR" != "$CURR_MAJOR" ]; then + OTHER_DEP_CHANGES="${OTHER_DEP_CHANGES}\n- **${dep}**: ${PREV_VER} โ†’ ${CURR_VER} (Major version change)" + fi + fi + done + ``` + +### Step 8: Generate Comprehensive Release Notes 1. Extract commit messages since base release: ```bash @@ -185,42 +210,52 @@ echo "Last stable release: $LAST_STABLE" echo "WARNING: PR #$PR not on main branch!" done ``` -3. Group by type: - - ๐Ÿš€ **Features** (feat:) - - ๐Ÿ› **Bug Fixes** (fix:) - - ๐Ÿ’ฅ **Breaking Changes** (BREAKING CHANGE) - - ๐Ÿ“š **Documentation** (docs:) - - ๐Ÿ”ง **Maintenance** (chore:, refactor:) - - โฌ†๏ธ **Dependencies** (deps:, dependency updates) -4. Include PR numbers and links -5. Add issue references (Fixes #123) -6. **Save changelog locally:** +3. Create standardized release notes using this exact template: ```bash - # Save to dated file for history - echo "$CHANGELOG" > release-notes-${NEW_VERSION}-$(date +%Y%m%d).md - - # Save to current for easy access - echo "$CHANGELOG" > CURRENT_RELEASE_NOTES.md - ``` -7. **CHANGELOG REVIEW**: Verify all PRs listed are actually on main branch + cat > release-notes-${NEW_VERSION}.md << 'EOF' + ## โš ๏ธ Breaking Changes + + - Breaking change description (#PR_NUMBER) -### Step 8: Create Enhanced Release Notes + --- -1. Create comprehensive user-facing release notes including: - - **What's New**: Major features and improvements - - **Bug Fixes**: User-visible fixes - - **Breaking Changes**: Migration guide if applicable - - **Dependencies**: Major dependency updates - - **Performance**: Notable performance improvements - - **Contributors**: Thank contributors for their work -2. Reference related documentation updates -3. Include screenshots for UI changes (if available) -4. **Save release notes:** - ```bash - # Enhanced release notes for GitHub - echo "$RELEASE_NOTES" > github-release-notes-${NEW_VERSION}.md + ## What's Changed + + ### ๐Ÿš€ Features + + - Feature description (#PR_NUMBER) + + ### ๐Ÿ› Bug Fixes + + - Bug fix description (#PR_NUMBER) + + ### ๐Ÿ”ง Maintenance + + - Maintenance item description (#PR_NUMBER) + + ### ๐Ÿ“š Documentation + + - Documentation update description (#PR_NUMBER) + + ### โฌ†๏ธ Dependencies + + - Updated dependency from vX.X.X to vY.Y.Y (#PR_NUMBER) + + **Full Changelog**: https://github.com/Comfy-Org/ComfyUI_frontend/compare/${BASE_TAG}...v${NEW_VERSION} + EOF ``` -5. **CONTENT REVIEW**: Release notes clear and helpful for users? +4. **Parse commits and populate template:** + - Group commits by conventional commit type (feat:, fix:, chore:, etc.) + - Extract PR numbers from commit messages + - For breaking changes, analyze if changes affect: + - Public APIs (app object, api module) + - Extension/workspace manager APIs + - Node schema, workflow schema, or other public schemas + - Any other public-facing interfaces + - For dependency updates, list version changes with PR numbers + - Remove empty sections (e.g., if no documentation changes) + - Ensure consistent bullet format: `- Description (#PR_NUMBER)` +5. **CONTENT REVIEW**: Release notes follow standard format? ### Step 9: Create Version Bump PR @@ -258,52 +293,17 @@ echo "Workflow triggered. Waiting for PR creation..." # For manual PRs gh pr create --title "${NEW_VERSION}" \ - --body-file enhanced-pr-description.md \ + --body-file release-notes-${NEW_VERSION}.md \ --label "Release" ``` -3. **Create enhanced PR description:** +3. **Update PR with release notes:** ```bash - cat > enhanced-pr-description.md << EOF - # Release v${NEW_VERSION} - - ## Version Change - \`${CURRENT_VERSION}\` โ†’ \`${NEW_VERSION}\` (${VERSION_TYPE}) - - ## Changelog - ${CHANGELOG} - - ## Breaking Changes - ${BREAKING_CHANGES} - - ## Testing Performed - - โœ… Full test suite (unit, component, browser) - - โœ… TypeScript compilation - - โœ… Linting checks - - โœ… Build verification - - โœ… Security audit - - ## Distribution Channels - - GitHub Release (with dist.zip) - - PyPI Package (comfyui-frontend-package) - - npm Package (@comfyorg/comfyui-frontend-types) - - ## Post-Release Tasks - - [ ] Verify all distribution channels - - [ ] Update external documentation - - [ ] Monitor for issues - EOF + # For workflow-created PRs, update the body with our release notes + gh pr edit ${PR_NUMBER} --body-file release-notes-${NEW_VERSION}.md ``` -4. Update PR with enhanced description: - ```bash - gh pr edit ${PR_NUMBER} --body-file enhanced-pr-description.md - ``` -5. Add changelog as comment for easy reference: - ```bash - gh pr comment ${PR_NUMBER} --body-file CURRENT_RELEASE_NOTES.md - ``` -6. **PR REVIEW**: Version bump PR created and enhanced correctly? +4. **PR REVIEW**: Version bump PR created with standardized release notes? -### Step 11: Critical Release PR Verification +### Step 10: Critical Release PR Verification 1. **CRITICAL**: Verify PR has "Release" label: ```bash @@ -325,7 +325,7 @@ echo "Workflow triggered. Waiting for PR creation..." ``` 7. **FINAL CODE REVIEW**: Release label present and no [skip ci]? -### Step 12: Pre-Merge Validation +### Step 11: Pre-Merge Validation 1. **Review Requirements**: Release PRs require approval 2. Monitor CI checks - watch for update-locales @@ -333,7 +333,7 @@ echo "Workflow triggered. Waiting for PR creation..." 4. Check no new commits to main since PR creation 5. **DEPLOYMENT READINESS**: Ready to merge? -### Step 13: Execute Release +### Step 12: Execute Release 1. **FINAL CONFIRMATION**: Merge PR to trigger release? 2. Merge the Release PR: @@ -345,6 +345,14 @@ echo "Workflow triggered. Waiting for PR creation..." sleep 10 gh run list --workflow=release.yaml --limit=1 ``` +4. **For Minor/Major Version Releases**: The create-release-candidate-branch workflow will automatically: + - Create a `core/x.yy` branch for the PREVIOUS minor version + - Apply branch protection rules + - Document the feature freeze policy + ```bash + # Monitor branch creation (for minor/major releases) + gh run list --workflow=create-release-candidate-branch.yaml --limit=1 + ``` 4. If workflow didn't trigger due to [skip ci]: ```bash echo "ERROR: Release workflow didn't trigger!" @@ -358,7 +366,7 @@ echo "Workflow triggered. Waiting for PR creation..." gh run watch ${WORKFLOW_RUN_ID} ``` -### Step 14: Enhance GitHub Release +### Step 13: Enhance GitHub Release 1. Wait for automatic release creation: ```bash @@ -371,10 +379,10 @@ echo "Workflow triggered. Waiting for PR creation..." 2. **Enhance the GitHub release:** ```bash - # Update release with our enhanced notes + # Update release with our release notes gh release edit v${NEW_VERSION} \ --title "๐Ÿš€ ComfyUI Frontend v${NEW_VERSION}" \ - --notes-file github-release-notes-${NEW_VERSION}.md \ + --notes-file release-notes-${NEW_VERSION}.md \ --latest # Add any additional assets if needed @@ -386,7 +394,7 @@ echo "Workflow triggered. Waiting for PR creation..." gh release view v${NEW_VERSION} ``` -### Step 15: Verify Multi-Channel Distribution +### Step 14: Verify Multi-Channel Distribution 1. **GitHub Release:** ```bash @@ -424,7 +432,7 @@ echo "Workflow triggered. Waiting for PR creation..." 4. **DISTRIBUTION VERIFICATION**: All channels published successfully? -### Step 16: Post-Release Monitoring Setup +### Step 15: Post-Release Monitoring Setup 1. **Monitor immediate release health:** ```bash @@ -492,8 +500,7 @@ echo "Workflow triggered. Waiting for PR creation..." - Plan next release cycle ## Files Generated - - \`release-notes-${NEW_VERSION}-$(date +%Y%m%d).md\` - Detailed changelog - - \`github-release-notes-${NEW_VERSION}.md\` - GitHub release notes + - \`release-notes-${NEW_VERSION}.md\` - Comprehensive release notes - \`post-release-checklist.md\` - Follow-up tasks EOF ``` @@ -544,7 +551,7 @@ echo "- GitHub: Update release with warning notes" The command implements multiple quality gates: 1. **๐Ÿ”’ Security Gate**: Vulnerability scanning, secret detection -2. **๐Ÿงช Quality Gate**: Full test suite, linting, type checking +2. **๐Ÿงช Quality Gate**: Unit and component tests, linting, type checking 3. **๐Ÿ“‹ Content Gate**: Changelog accuracy, release notes quality 4. **๐Ÿ”„ Process Gate**: Release timing verification 5. **โœ… Verification Gate**: Multi-channel publishing confirmation @@ -582,44 +589,46 @@ The command implements multiple quality gates: - Draft release status - Python package specs require that prereleases use alpha/beta/rc as the preid -## Common Issues and Solutions +## Critical Implementation Notes -### Issue: Pre-release Version Confusion -**Problem**: Not sure whether to promote pre-release or create new version -**Solution**: -- Follow semver standards: a prerelease version is followed by a normal release. It should have the same major, minor, and patch versions as the prerelease. +When executing this release process, pay attention to these key aspects: -### Issue: Wrong Commit Count -**Problem**: Changelog includes commits from other branches -**Solution**: Always use `--first-parent` flag with git log +### Version Handling +- For pre-release versions (e.g., 1.24.0-rc.1), the next stable release should be the same version without the suffix (1.24.0) +- Never skip version numbers - follow semantic versioning strictly -**Update**: Sometimes update-locales doesn't add [skip ci] - always verify! +### Commit History Analysis +- **ALWAYS** use `--first-parent` flag with git log to avoid including commits from merged feature branches +- Verify PR merge targets before including them in changelogs: + ```bash + gh pr view ${PR_NUMBER} --json baseRefName + ``` -### Issue: Missing PRs in Changelog -**Problem**: PR was merged to different branch -**Solution**: Verify PR merge target with: -```bash -gh pr view ${PR_NUMBER} --json baseRefName -``` +### Release Workflow Triggers +- The "Release" label on the PR is **CRITICAL** - without it, PyPI/npm publishing won't occur +- Check for `[skip ci]` in commit messages before merging - this blocks the release workflow +- If you encounter `[skip ci]`, push an empty commit to override it: + ```bash + git commit --allow-empty -m "Trigger release workflow" + ``` -### Issue: Release Failed Due to [skip ci] -**Problem**: Release workflow didn't trigger after merge -**Prevention**: Always avoid this scenario -- Ensure that `[skip ci]` or similar flags are NOT in the `HEAD` commit message of the PR - - Push a new, empty commit to the PR -- Always double-check this immediately before merging +### PR Creation Details +- Version bump PRs come from `comfy-pr-bot`, not `github-actions` +- The workflow typically completes in 20-30 seconds +- Always wait for the PR to be created before trying to edit it -**Recovery Strategy**: -1. Revert version in a new PR (e.g., 1.24.0 โ†’ 1.24.0-1) -2. Merge the revert PR -3. Run version bump workflow again -4. This creates a fresh PR without [skip ci] -Benefits: Cleaner than creating extra version numbers +### Breaking Changes Detection +- Analyze changes to public-facing APIs: + - The `app` object and its methods + - The `api` module exports + - Extension and workspace manager interfaces + - Node schema, workflow schema, and other public schemas +- Any modifications to these require marking as breaking changes -## Key Learnings & Notes - -1. **PR Author**: Version bump PRs are created by `comfy-pr-bot`, not `github-actions` -2. **Workflow Speed**: Version bump workflow typically completes in ~20-30 seconds -3. **Update-locales Behavior**: Inconsistent - sometimes adds [skip ci], sometimes doesn't -4. **Recovery Options**: Reverting version is cleaner than creating extra versions +### Recovery Procedures +If the release workflow fails to trigger: +1. Create a revert PR to restore the previous version +2. Merge the revert +3. Re-run the version bump workflow +4. This approach is cleaner than creating extra version numbers diff --git a/.claude/commands/create-hotfix-release.md b/.claude/commands/create-hotfix-release.md index b1e521a29..5dd294698 100644 --- a/.claude/commands/create-hotfix-release.md +++ b/.claude/commands/create-hotfix-release.md @@ -138,14 +138,50 @@ For each commit: ```bash gh pr create --base core/X.Y --head release/1.23.5 \ --title "[Release] v1.23.5" \ - --body "..." \ + --body "Release notes will be added shortly..." \ --label "Release" ``` 3. **CRITICAL**: Verify "Release" label is added -4. PR description should include: - - Version: `1.23.4` โ†’ `1.23.5` - - Included fixes (link to previous PR) - - Release notes for users +4. Create standardized release notes: + ```bash + cat > release-notes-${NEW_VERSION}.md << 'EOF' + ## โš ๏ธ Breaking Changes + + - Breaking change description (#PR_NUMBER) + + --- + + ## What's Changed + + ### ๐Ÿš€ Features + + - Feature description (#PR_NUMBER) + + ### ๐Ÿ› Bug Fixes + + - Bug fix description (#PR_NUMBER) + + ### ๐Ÿ”ง Maintenance + + - Maintenance item description (#PR_NUMBER) + + ### ๐Ÿ“š Documentation + + - Documentation update description (#PR_NUMBER) + + ### โฌ†๏ธ Dependencies + + - Updated dependency from vX.X.X to vY.Y.Y (#PR_NUMBER) + + **Full Changelog**: https://github.com/Comfy-Org/ComfyUI_frontend/compare/v${CURRENT_VERSION}...v${NEW_VERSION} + EOF + ``` + - For hotfixes, typically only populate the "Bug Fixes" section + - Include links to the cherry-picked PRs/commits + - Update the PR body with the release notes: + ```bash + gh pr edit ${PR_NUMBER} --body-file release-notes-${NEW_VERSION}.md + ``` 5. **CONFIRMATION REQUIRED**: Release PR has "Release" label? ### Step 11: Monitor Release Process diff --git a/.claude/commands/pr.md b/.claude/commands/pr.md new file mode 100644 index 000000000..a9459a5a6 --- /dev/null +++ b/.claude/commands/pr.md @@ -0,0 +1,131 @@ +# Create PR + +Automate PR creation with proper tags, labels, and concise summary. + +## Step 1: Check Prerequisites + +```bash +# Ensure you have uncommitted changes +git status + +# If changes exist, commit them first +git add . +git commit -m "[tag] Your commit message" +``` + +## Step 2: Push and Create PR + +You'll create the PR with the following structure: + +### PR Tags (use in title) + +- `[feat]` - New features โ†’ label: `enhancement` +- `[bugfix]` - Bug fixes โ†’ label: `verified bug` +- `[refactor]` - Code restructuring โ†’ label: `enhancement` +- `[docs]` - Documentation โ†’ label: `documentation` +- `[test]` - Test changes โ†’ label: `enhancement` +- `[ci]` - CI/CD changes โ†’ label: `enhancement` + +### Label Mapping + +#### General Labels + +- Feature/Enhancement: `enhancement` +- Bug fixes: `verified bug` +- Documentation: `documentation` +- Dependencies: `dependencies` +- Performance: `Performance` +- Desktop app: `Electron` + +#### Product Area Labels + +**Core Features** + +- `area:nodes` - Node-related functionality +- `area:workflows` - Workflow management +- `area:queue` - Queue system +- `area:models` - Model handling +- `area:templates` - Template system +- `area:subgraph` - Subgraph functionality + +**UI Components** + +- `area:ui` - General user interface improvements +- `area:widgets` - Widget system +- `area:dom-widgets` - DOM-based widgets +- `area:links` - Connection links between nodes +- `area:groups` - Node grouping functionality +- `area:reroutes` - Reroute nodes +- `area:previews` - Preview functionality +- `area:minimap` - Minimap navigation +- `area:floating-toolbox` - Floating toolbar +- `area:mask-editor` - Mask editing tools + +**Navigation & Organization** + +- `area:navigation` - Navigation system +- `area:search` - Search functionality +- `area:workspace-management` - Workspace features +- `area:topbar-menu` - Top bar menu +- `area:help-menu` - Help menu system + +**System Features** + +- `area:settings` - Settings/preferences +- `area:hotkeys` - Keyboard shortcuts +- `area:undo-redo` - Undo/redo system +- `area:customization` - Customization features +- `area:auth` - Authentication +- `area:comms` - Communication/networking + +**Development & Infrastructure** + +- `area:CI/CD` - CI/CD pipeline +- `area:testing` - Testing infrastructure +- `area:vue-migration` - Vue migration work +- `area:manager` - ComfyUI Manager integration + +**Platform-Specific** + +- `area:mobile` - Mobile support +- `area:3d` - 3D-related features + +**Special Areas** + +- `area:i18n` - Translation/internationalization +- `area:CNR` - Comfy Node Registry + +## Step 3: Execute PR Creation + +```bash +# First, push your branch +git push -u origin $(git branch --show-current) + +# Then create the PR (replace placeholders) +gh pr create \ + --title "[TAG] Brief description" \ + --body "$(cat <<'EOF' +## Summary +One sentence describing what changed and why. + +## Changes +- **What**: Core functionality added/modified +- **Breaking**: Any breaking changes (if none, omit this line) +- **Dependencies**: New dependencies (if none, omit this line) + +## Review Focus +- Critical design decisions or edge cases that need attention + +Fixes #ISSUE_NUMBER +EOF +)" \ + --label "APPROPRIATE_LABEL" \ + --base main +``` + +## Additional Options + +- Add multiple labels: `--label "enhancement,Performance"` +- Request reviewers: `--reviewer @username` +- Mark as draft: `--draft` +- Open in browser after creation: `--web` diff --git a/.cursor/rules/unit-test.mdc b/.cursor/rules/unit-test.mdc new file mode 100644 index 000000000..2c6704f3e --- /dev/null +++ b/.cursor/rules/unit-test.mdc @@ -0,0 +1,21 @@ +--- +description: Creating unit tests +globs: +alwaysApply: false +--- + +# Creating unit tests + +- This project uses `vitest` for unit testing +- Tests are stored in the `test/` directory +- Tests should be cross-platform compatible; able to run on Windows, macOS, and linux + - e.g. the use of `path.resolve`, or `path.join` and `path.sep` to ensure that tests work the same on all platforms +- Tests should be mocked properly + - Mocks should be cleanly written and easy to understand + - Mocks should be re-usable where possible + +## Unit test style + +- Prefer the use of `test.extend` over loose variables + - To achieve this, import `test as baseTest` from `vitest` +- Never use `it`; `test` should be used in place of this \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 37d931349..af4b6adbc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ *.ts text eol=lf *.vue text eol=lf *.js text eol=lf + +# Generated files +src/types/comfyRegistryTypes.ts linguist-generated=true +src/types/generatedManagerTypes.ts linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index 4c8868d00..d4a309bf8 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -1,99 +1,106 @@ name: Bug Report -description: 'Something is not behaving as expected.' +description: 'Report something that is not working correctly' title: '[Bug]: ' labels: ['Potential Bug'] type: Bug body: - - type: markdown - attributes: - value: | - Before submitting a **Bug Report**, please ensure the following: - - - **1:** You are running the latest version of ComfyUI. - - **2:** You have looked at the existing bug reports and made sure this isn't already reported. - - type: checkboxes - id: custom-nodes-test attributes: - label: Custom Node Testing - description: Please confirm you have tried to reproduce the issue with all custom nodes disabled. + label: Prerequisites options: - - label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help) + - label: I am running the latest version of ComfyUI + required: true + - label: I have searched existing issues to make sure this isn't a duplicate + required: true + - label: I have tested with all custom nodes disabled ([see how](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled)) required: true - type: textarea + id: description attributes: - label: Frontend Version - description: | - What is the frontend version you are using? You can check this in the settings dialog. - -
- - Click to show where to find the version - - Open the setting by clicking the cog icon in the bottom-left of the screen, then click `About`. - - ![Frontend version](https://github.com/user-attachments/assets/561fb7c3-3012-457c-a494-9bdc1ff035c0) - -
- validations: - required: true - - type: textarea - attributes: - label: Expected Behavior - description: 'What you expected to happen.' - validations: - required: true - - type: textarea - attributes: - label: Actual Behavior - description: 'What actually happened. Please include a screenshot / video clip of the issue if possible.' + label: What happened? + description: A clear and concise description of the bug. Include screenshots or videos if helpful. + placeholder: | + Example: "When I connect a VAE Decode node to a KSampler, the connection line appears but the workflow fails to execute with an error message..." validations: required: true + - type: textarea + id: reproduce attributes: label: Steps to Reproduce - description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than ComfyUI, in which case it should be reported to the node's author." - validations: - required: true - - type: textarea - attributes: - label: Debug Logs - description: 'Please copy the output from your terminal logs here.' - render: powershell - validations: - required: true - - type: textarea - attributes: - label: Browser Logs - description: 'Please copy the output from your browser logs here. You can access this by pressing F12 to toggle the developer tools, then navigating to the Console tab.' - validations: - required: true - - type: textarea - attributes: - label: Setting JSON - description: 'Please upload the setting file here. The setting file is located at `user/default/comfy.settings.json`' + description: How can we reproduce this issue? Please attach your workflow (JSON or PNG). + placeholder: | + 1. Add a KSampler node + 2. Connect it to... + 3. Click Queue Prompt + 4. See error + value: | + 1. + 2. + 3. validations: required: true + - type: dropdown - id: browsers + id: severity attributes: - label: What browsers do you use to access the UI ? - multiple: true + label: How is this affecting you? options: - - Mozilla Firefox - - Google Chrome - - Brave - - Apple Safari - - Microsoft Edge - - Android - - iOS - - Other - - type: textarea - attributes: - label: Other Information - description: 'Any other context, details, or screenshots that might help solve the issue.' - placeholder: 'Add any other relevant information here...' + - Crashes ComfyUI completely + - Workflow won't execute + - Feature doesn't work as expected + - Visual/UI issue only + - Minor inconvenience validations: - required: false + required: true + + - type: input + id: version + attributes: + label: ComfyUI Frontend Version + description: Found in Settings > About (e.g., "1.3.45") + placeholder: "1.3.45" + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: Browser + description: Which browser are you using? + options: + - Chrome/Chromium + - Firefox + - Safari + - Edge + - Other + validations: + required: true + + - type: markdown + attributes: + value: | + ## Additional Information (Optional) + *The following fields help us debug complex issues but are not required for most bug reports.* + + - type: textarea + id: console-errors + attributes: + label: Console Errors + description: If you see red error messages in the browser console (F12), paste them here + render: javascript + + - type: textarea + id: logs + attributes: + label: Logs + description: If relevant, paste any terminal/server logs here + render: shell + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other information that might help (OS, GPU, specific nodes involved, etc.) diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml index dc1667a6f..a32598374 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -1,6 +1,6 @@ name: Feature Request -description: Suggest an idea for this project -title: '[Feature Request]: ' +description: Report a problem or limitation you're experiencing +title: '[Feature]: ' labels: ['enhancement'] type: Feature @@ -8,34 +8,74 @@ body: - type: checkboxes attributes: label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit. + description: Please search to see if an issue already exists for the problem you're experiencing, and that it's not addressed in a recent build/commit. options: - label: I have searched the existing issues and checked the recent builds/commits required: true - type: markdown attributes: value: | - *Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible* + *Please focus on describing the problem you're experiencing rather than proposing specific solutions. This helps us design the best possible solution for you and other users.* - type: textarea - id: feature + id: problem attributes: - label: What would your feature do ? - description: Tell us about your feature in a very clear and simple way, and what problem it would solve + label: What problem are you experiencing? + description: Describe the issue or limitation you're facing in your workflow + placeholder: | + Example: "I frequently lose work when switching between different projects because there's no way to save my current workspace state" + NOT: "Add a save button that exports the workspace" validations: required: true - type: textarea - id: workflow + id: context attributes: - label: Proposed workflow - description: Please provide us with step by step information on how you'd like the feature to be accessed and used - value: | - 1. Go to .... - 2. Press .... - 3. ... + label: When does this problem occur? + description: Describe the specific situations or workflows where you encounter this issue + placeholder: | + - When working with large node graphs... + - During batch processing workflows... + - While collaborating with team members... + validations: + required: true + - type: dropdown + id: frequency + attributes: + label: How often do you encounter this problem? + options: + - Multiple times per day + - Daily + - Several times per week + - Weekly + - Occasionally + - Rarely + validations: + required: true + - type: dropdown + id: impact + attributes: + label: How much does this problem affect your workflow? + description: Help us understand the severity of this issue for you + options: + - Blocks me from completing tasks + - Significantly slows down my work + - Causes moderate inconvenience + - Minor annoyance validations: required: true - type: textarea - id: misc + id: workaround attributes: - label: Additional information - description: Add any other context or screenshots about the feature request here. + label: Current workarounds + description: How do you currently deal with this problem, if at all? + placeholder: | + Example: "I manually export and reimport nodes between projects, which takes 10-15 minutes each time" + - type: textarea + id: ideas + attributes: + label: Ideas for solutions (Optional) + description: If you have thoughts on potential solutions, feel free to share them here. However, we'll explore all possible options to find the best approach. + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context, screenshots, or examples that help illustrate the problem. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..742e2527b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## Summary + + + +## Changes + +- **What**: +- **Breaking**: +- **Dependencies**: + +## Review Focus + + + + + + +## Screenshots (if applicable) + + diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 000000000..f9106caee --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,165 @@ +name: Auto Backport + +on: + pull_request_target: + types: [closed] + branches: [main] + +jobs: + backport: + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Extract version labels + id: versions + run: | + # Extract version labels (e.g., "1.24", "1.22") + VERSIONS="" + LABELS='${{ toJSON(github.event.pull_request.labels) }}' + for label in $(echo "$LABELS" | jq -r '.[].name'); do + # Match version labels like "1.24" (major.minor only) + if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then + # Validate the branch exists before adding to list + if git ls-remote --exit-code origin "core/${label}" >/dev/null 2>&1; then + VERSIONS="${VERSIONS}${label} " + else + echo "::warning::Label '${label}' found but branch 'core/${label}' does not exist" + fi + fi + done + + if [ -z "$VERSIONS" ]; then + echo "::error::No version labels found (e.g., 1.24, 1.22)" + exit 1 + fi + + echo "versions=${VERSIONS}" >> $GITHUB_OUTPUT + echo "Found version labels: ${VERSIONS}" + + - name: Backport commits + id: backport + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} + run: | + FAILED="" + SUCCESS="" + + for version in ${{ steps.versions.outputs.versions }}; do + echo "::group::Backporting to core/${version}" + + TARGET_BRANCH="core/${version}" + BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${version}" + + # Fetch target branch (fail if doesn't exist) + if ! git fetch origin "${TARGET_BRANCH}"; then + echo "::error::Target branch ${TARGET_BRANCH} does not exist" + FAILED="${FAILED}${version}:branch-missing " + echo "::endgroup::" + continue + fi + + # Create backport branch + git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}" + + # Try cherry-pick + if git cherry-pick "${MERGE_COMMIT}"; then + git push origin "${BACKPORT_BRANCH}" + SUCCESS="${SUCCESS}${version}:${BACKPORT_BRANCH} " + echo "Successfully created backport branch: ${BACKPORT_BRANCH}" + # Return to main (keep the branch, we need it for PR) + git checkout main + else + # Get conflict info + CONFLICTS=$(git diff --name-only --diff-filter=U | tr '\n' ',') + git cherry-pick --abort + + echo "::error::Cherry-pick failed due to conflicts" + FAILED="${FAILED}${version}:conflicts:${CONFLICTS} " + + # Clean up the failed branch + git checkout main + git branch -D "${BACKPORT_BRANCH}" + fi + + echo "::endgroup::" + done + + echo "success=${SUCCESS}" >> $GITHUB_OUTPUT + echo "failed=${FAILED}" >> $GITHUB_OUTPUT + + if [ -n "${FAILED}" ]; then + exit 1 + fi + + - name: Create PR for each successful backport + if: steps.backport.outputs.success + env: + GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + PR_NUMBER="${{ github.event.pull_request.number }}" + PR_AUTHOR="${{ github.event.pull_request.user.login }}" + + for backport in ${{ steps.backport.outputs.success }}; do + IFS=':' read -r version branch <<< "${backport}" + + if PR_URL=$(gh pr create \ + --base "core/${version}" \ + --head "${branch}" \ + --title "[backport ${version}] ${PR_TITLE}" \ + --body "Backport of #${PR_NUMBER} to \`core/${version}\`"$'\n\n'"Automatically created by backport workflow." \ + --label "backport" 2>&1); then + + # Extract PR number from URL + PR_NUM=$(echo "${PR_URL}" | grep -o '[0-9]*$') + + if [ -n "${PR_NUM}" ]; then + gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}" + fi + else + echo "::error::Failed to create PR for ${version}: ${PR_URL}" + # Still try to comment on the original PR about the failure + gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport branch created but PR creation failed for \`core/${version}\`. Please create the PR manually from branch \`${branch}\`" + fi + done + + - name: Comment on failures + if: failure() && steps.backport.outputs.failed + env: + GH_TOKEN: ${{ github.token }} + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + PR_AUTHOR="${{ github.event.pull_request.user.login }}" + MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" + + for failure in ${{ steps.backport.outputs.failed }}; do + IFS=':' read -r version reason conflicts <<< "${failure}" + + if [ "${reason}" = "branch-missing" ]; then + gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`core/${version}\` does not exist" + + elif [ "${reason}" = "conflicts" ]; then + # Convert comma-separated conflicts back to newlines for display + CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /') + + COMMENT_BODY="@${PR_AUTHOR} Backport to \`core/${version}\` failed: Merge conflicts detected."$'\n\n'"Please manually cherry-pick commit \`${MERGE_COMMIT}\` to the \`core/${version}\` branch."$'\n\n'"
Conflicting files"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"
" + gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" + fi + done diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 1b7f86934..5b0c0ffbd 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -4,6 +4,8 @@ permissions: contents: read pull-requests: write issues: write + id-token: write + statuses: write on: pull_request: @@ -17,10 +19,10 @@ jobs: should-proceed: ${{ steps.check-status.outputs.proceed }} steps: - name: Wait for other CI checks - uses: lewagon/wait-on-check-action@v1.3.1 + uses: lewagon/wait-on-check-action@e106e5c43e8ca1edea6383a39a01c5ca495fd812 with: ref: ${{ github.event.pull_request.head.sha }} - check-regexp: '^(ESLint|Prettier Check|Tests CI|Vitest Tests)' + check-regexp: '^(lint-and-format|test|playwright-tests)' wait-interval: 30 repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -28,7 +30,7 @@ jobs: id: check-status run: | # Get all check runs for this commit - CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("ESLint|Prettier Check|Tests CI|Vitest Tests")) | {name, conclusion}') + CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format|test|playwright-tests")) | {name, conclusion}') # Check if any required checks failed if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; then @@ -63,10 +65,17 @@ jobs: - name: Run Claude PR Review uses: anthropics/claude-code-action@main with: - prompt_file: .claude/commands/comprehensive-pr-review.md + label_trigger: "claude-review" + direct_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. + Each issue must be posted as a separate inline comment on the specific line of code. anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - max_turns: 1 + max_turns: 256 timeout_minutes: 30 + allowed_tools: "Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch" env: PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-release-candidate-branch.yaml b/.github/workflows/create-release-candidate-branch.yaml new file mode 100644 index 000000000..84b545478 --- /dev/null +++ b/.github/workflows/create-release-candidate-branch.yaml @@ -0,0 +1,214 @@ +name: Create Release Branch + +on: + pull_request: + types: [closed] + branches: [main] + paths: + - 'package.json' + +jobs: + create-release-branch: + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + contains(github.event.pull_request.labels.*.name, 'Release') + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - name: Check version bump type + id: check_version + run: | + # Get current version from main + CURRENT_VERSION=$(node -p "require('./package.json').version") + # Remove 'v' prefix if present (shouldn't be in package.json, but defensive) + CURRENT_VERSION=${CURRENT_VERSION#v} + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + # Validate version format + if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "ERROR: Invalid version format: $CURRENT_VERSION" + exit 1 + fi + + # Extract major and minor versions + MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1) + MINOR=$(echo $CURRENT_VERSION | cut -d. -f2) + PATCH=$(echo $CURRENT_VERSION | cut -d. -f3 | cut -d- -f1) + + echo "major=$MAJOR" >> $GITHUB_OUTPUT + echo "minor=$MINOR" >> $GITHUB_OUTPUT + echo "patch=$PATCH" >> $GITHUB_OUTPUT + + # Get previous version from the commit before the merge + git checkout HEAD^1 + PREV_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0") + # Remove 'v' prefix if present + PREV_VERSION=${PREV_VERSION#v} + + # Validate previous version format + if ! [[ "$PREV_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "WARNING: Invalid previous version format: $PREV_VERSION, using 0.0.0" + PREV_VERSION="0.0.0" + fi + + PREV_MINOR=$(echo $PREV_VERSION | cut -d. -f2) + + echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT + echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT + + # Get previous major version for comparison + PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1) + + # Check if current version is a pre-release + if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then + IS_PRERELEASE=true + else + IS_PRERELEASE=false + fi + + # Check if this was a minor version bump or major version bump + # But skip if it's a pre-release version + if [[ "$IS_PRERELEASE" == "true" ]]; then + echo "is_minor_bump=false" >> $GITHUB_OUTPUT + echo "reason=prerelease version" >> $GITHUB_OUTPUT + elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then + # Major version bump (e.g., 1.99.x โ†’ 2.0.0) + echo "is_minor_bump=true" >> $GITHUB_OUTPUT + BRANCH_NAME="core/${PREV_MAJOR}.${PREV_MINOR}" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then + # Minor version bump (e.g., 1.23.x โ†’ 1.24.0) + echo "is_minor_bump=true" >> $GITHUB_OUTPUT + BRANCH_NAME="core/${MAJOR}.${PREV_MINOR}" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + else + echo "is_minor_bump=false" >> $GITHUB_OUTPUT + fi + + # Return to main branch + git checkout main + + - name: Create release branch + if: steps.check_version.outputs.is_minor_bump == 'true' + run: | + BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}" + + # Check if branch already exists + if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then + echo "โš ๏ธ Branch $BRANCH_NAME already exists, skipping creation" + echo "branch_exists=true" >> $GITHUB_ENV + exit 0 + else + echo "branch_exists=false" >> $GITHUB_ENV + fi + + # Create branch from the commit BEFORE the version bump + # This ensures the release branch has the previous minor version + git checkout -b "$BRANCH_NAME" HEAD^1 + + # Push the new branch + git push origin "$BRANCH_NAME" + + echo "โœ… Created release branch: $BRANCH_NAME" + echo "This branch is now in feature freeze and will only receive:" + echo "- Bug fixes" + echo "- Critical security patches" + echo "- Documentation updates" + + - name: Create branch protection rules + if: steps.check_version.outputs.is_minor_bump == 'true' && env.branch_exists != 'true' + env: + GITHUB_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }} + run: | + BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}" + + # Create branch protection using GitHub API + echo "Setting up branch protection for $BRANCH_NAME..." + + RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/branches/$BRANCH_NAME/protection" \ + -d '{ + "required_status_checks": { + "strict": true, + "contexts": ["lint-and-format", "test", "playwright-tests"] + }, + "enforce_admins": false, + "required_pull_request_reviews": { + "required_approving_review_count": 1, + "dismiss_stale_reviews": true + }, + "restrictions": null, + "allow_force_pushes": false, + "allow_deletions": false + }') + + HTTP_CODE=$(echo "$RESPONSE" | tail -n 1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [[ "$HTTP_CODE" -eq 200 ]] || [[ "$HTTP_CODE" -eq 201 ]]; then + echo "โœ… Branch protection successfully applied" + else + echo "โš ๏ธ Failed to apply branch protection (HTTP $HTTP_CODE)" + echo "Response: $BODY" + # Don't fail the workflow, just warn + fi + + - name: Post summary + if: steps.check_version.outputs.is_minor_bump == 'true' + run: | + BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}" + PREV_VERSION="${{ steps.check_version.outputs.prev_version }}" + CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}" + + if [[ "${{ env.branch_exists }}" == "true" ]]; then + cat >> $GITHUB_STEP_SUMMARY << EOF + ## ๐ŸŒฟ Release Branch Already Exists + + The release branch for the previous minor version already exists: + EOF + else + cat >> $GITHUB_STEP_SUMMARY << EOF + ## ๐ŸŒฟ Release Branch Created + + A new release branch has been created for the previous minor version: + EOF + fi + + cat >> $GITHUB_STEP_SUMMARY << EOF + + - **Branch**: \`$BRANCH_NAME\` + - **Version**: \`$PREV_VERSION\` (feature frozen) + - **Main branch**: \`$CURRENT_VERSION\` (active development) + + ### Branch Policy + + The \`$BRANCH_NAME\` branch is now in **feature freeze** and will only accept: + - ๐Ÿ› Bug fixes + - ๐Ÿ”’ Security patches + - ๐Ÿ“š Documentation updates + + All new features should continue to be developed against \`main\`. + + ### Backporting Changes + + To backport a fix to this release branch: + 1. Create your fix on \`main\` first + 2. Cherry-pick to \`$BRANCH_NAME\` + 3. Create a PR targeting \`$BRANCH_NAME\` + 4. Use the \`Release\` label on the PR + EOF diff --git a/.github/workflows/eslint.yaml b/.github/workflows/eslint.yaml deleted file mode 100644 index 72ed6b8fb..000000000 --- a/.github/workflows/eslint.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: ESLint - -on: - pull_request: - branches: [ main, master, dev*, core/*, desktop/* ] - -jobs: - eslint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - run: npm ci - - run: npm run lint diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml deleted file mode 100644 index 23eef7f55..000000000 --- a/.github/workflows/format.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Prettier Check - -on: - pull_request: - branches: [ main, master, dev*, core/*, desktop/* ] - -jobs: - prettier: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - - name: Install dependencies - run: npm ci - - - name: Run Prettier check - run: npm run format:check diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml new file mode 100644 index 000000000..5d9a50d83 --- /dev/null +++ b/.github/workflows/lint-and-format.yaml @@ -0,0 +1,83 @@ +name: Lint and Format + +on: + pull_request: + branches-ignore: [wip/*, draft/*, temp/*] + +permissions: + contents: write + pull-requests: write + +jobs: + lint-and-format: + runs-on: ubuntu-latest + steps: + - name: Checkout PR + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint with auto-fix + run: npm run lint:fix + + - name: Run Prettier with auto-format + run: npm run format + + - name: Check for changes + id: verify-changed-files + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi + + - name: Commit changes + if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "[auto-fix] Apply ESLint and Prettier fixes" + git push + + - name: Final validation + run: | + npm run lint + npm run format:check + npm run knip + + - name: Comment on PR about auto-fix + if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '## ๐Ÿ”ง Auto-fixes Applied\n\nThis PR has been automatically updated to fix linting and formatting issues.\n\n**โš ๏ธ Important**: Your local branch is now behind. Run `git pull` before making additional changes to avoid conflicts.\n\n### Changes made:\n- ESLint auto-fixes\n- Prettier formatting' + }) + + - name: Comment on PR about manual fix needed + if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name != github.repository + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '## โš ๏ธ Linting/Formatting Issues Found\n\nThis PR has linting or formatting issues that need to be fixed.\n\n**Since this PR is from a fork, auto-fix cannot be applied automatically.**\n\n### Option 1: Set up pre-commit hooks (recommended)\nRun this once to automatically format code on every commit:\n```bash\nnpm run prepare\n```\n\n### Option 2: Fix manually\nRun these commands and push the changes:\n```bash\nnpm run lint:fix\nnpm run format\n```\n\nSee [CONTRIBUTING.md](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/CONTRIBUTING.md#git-pre-commit-hooks) for more details.' + }) \ No newline at end of file diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml index 580bdee19..39411ade3 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/test-ui.yaml @@ -4,7 +4,7 @@ on: push: branches: [main, master, core/*, desktop/*] pull_request: - branches: [main, master, dev*, core/*, desktop/*] + branches-ignore: [wip/*, draft/*, temp/*, vue-nodes-migration] jobs: setup: @@ -60,7 +60,7 @@ jobs: strategy: fail-fast: false matrix: - browser: [chromium, chromium-2x, mobile-chrome] + browser: [chromium, chromium-2x, chromium-0.5x, mobile-chrome] steps: - name: Wait for cache propagation run: sleep 10 diff --git a/.github/workflows/update-litegraph.yaml b/.github/workflows/update-litegraph.yaml deleted file mode 100644 index bc77f3a55..000000000 --- a/.github/workflows/update-litegraph.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: Update Litegraph Dependency - -on: - workflow_dispatch: - -jobs: - update-litegraph: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Update litegraph - run: npm install @comfyorg/litegraph@latest - - - name: Get new version - id: get-version - run: | - NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('./package-lock.json')).packages['node_modules/@comfyorg/litegraph'].version)") - echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT - - - name: Create Pull Request - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e - with: - token: ${{ secrets.PR_GH_TOKEN }} - commit-message: '[chore] Update litegraph to ${{ steps.get-version.outputs.NEW_VERSION }}' - title: '[chore] Update litegraph to ${{ steps.get-version.outputs.NEW_VERSION }}' - body: | - Automated update of litegraph to version ${{ steps.get-version.outputs.NEW_VERSION }}. - Ref: https://github.com/Comfy-Org/litegraph.js/releases/tag/v${{ steps.get-version.outputs.NEW_VERSION }} - branch: update-litegraph-${{ steps.get-version.outputs.NEW_VERSION }} - base: main - labels: | - dependencies diff --git a/.github/workflows/update-manager-types.yaml b/.github/workflows/update-manager-types.yaml index bbc1d30a5..7b3a340ba 100644 --- a/.github/workflows/update-manager-types.yaml +++ b/.github/workflows/update-manager-types.yaml @@ -3,6 +3,11 @@ name: Update ComfyUI-Manager API Types on: # Manual trigger workflow_dispatch: + inputs: + target_branch: + description: 'Target branch for the PR' + required: true + default: 'main' jobs: update-manager-types: @@ -70,7 +75,7 @@ jobs: - name: Create Pull Request if: steps.check-changes.outputs.changed == 'true' - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e with: token: ${{ secrets.PR_GH_TOKEN }} commit-message: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}' @@ -85,7 +90,7 @@ jobs: These types are automatically generated using openapi-typescript. branch: update-manager-types-${{ steps.manager-info.outputs.commit }} - base: main + base: ${{ inputs.target_branch }} labels: Manager delete-branch: true add-paths: | diff --git a/.github/workflows/vitest.yaml b/.github/workflows/vitest.yaml index a2f47c342..788b6aba4 100644 --- a/.github/workflows/vitest.yaml +++ b/.github/workflows/vitest.yaml @@ -4,7 +4,7 @@ on: push: branches: [ main, master, dev*, core/*, desktop/* ] pull_request: - branches: [ main, master, dev*, core/*, desktop/* ] + branches-ignore: [ wip/*, draft/*, temp/* ] jobs: test: diff --git a/.gitignore b/.gitignore index e4fe57083..b49947757 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ node_modules dist dist-ssr *.local +# Claude configuration +.claude/*.local.json # Editor directories and files .vscode/* diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 index a8866207a..6b8a399e4 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,9 +1,4 @@ -if [[ "$OS" == "Windows_NT" ]]; then - npx.cmd lint-staged - # Check for unused i18n keys in staged files - npx.cmd tsx scripts/check-unused-i18n-keys.ts -else - npx lint-staged - # Check for unused i18n keys in staged files - npx tsx scripts/check-unused-i18n-keys.ts -fi +#!/usr/bin/env bash + +npx lint-staged +npx tsx scripts/check-unused-i18n-keys.ts \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index 445ad128b..82e215be0 100644 --- a/.mcp.json +++ b/.mcp.json @@ -3,6 +3,10 @@ "playwright": { "command": "npx", "args": ["-y", "@executeautomation/playwright-mcp-server"] + }, + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp"] } } } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..f01701cf4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Source: `src/` (Vue 3 + TypeScript). Key areas: `components/`, `views/`, `stores/` (Pinia), `composables/`, `services/`, `utils/`, `assets/`, `locales/`. +- 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`. + +## Build, Test, and Development Commands +- `npm run dev`: Start Vite dev server. +- `npm run dev:electron`: Dev server with Electron API mocks. +- `npm run build`: Type-check then production build to `dist/`. +- `npm run preview`: Preview the production build locally. +- `npm run test:unit`: Run Vitest unit tests (`tests-ui/`). +- `npm run test:component`: Run component tests (`src/components/`). +- `npm run test:browser`: Run Playwright E2E tests (`browser_tests/`). +- `npm run lint` / `npm run lint:fix`: Lint (ESLint). `npm run format` / `format:check`: Prettier. +- `npm run typecheck`: Vue TSC type checking. + +## Coding Style & Naming Conventions +- Language: TypeScript, Vue SFCs (`.vue`). Indent 2 spaces; single quotes; no semicolons; width 80 (see `.prettierrc`). +- Imports: sorted/grouped by plugin; run `npm run format` before committing. +- ESLint: Vue + TS rules; no floating promises; unused imports disallowed; i18n raw text restrictions in templates. +- Naming: Vue components in PascalCase (e.g., `MenuHamburger.vue`); composables `useXyz.ts`; Pinia stores `*Store.ts`. + +## Testing Guidelines +- Frameworks: Vitest (unit/component, happy-dom) and Playwright (E2E). +- Test files: `**/*.{test,spec}.{ts,tsx,js}` under `tests-ui/`, `src/components/`, and `src/lib/litegraph/test/`. +- Coverage: text/json/html reporters enabled; aim to cover critical logic and new features. +- Playwright: place tests in `browser_tests/`; optional tags like `@mobile`, `@2x` are respected by config. + +## Commit & Pull Request Guidelines +- Commits: Prefer Conventional Commits (e.g., `feat(ui): add sidebar`), `refactor(litegraph): โ€ฆ`. Use `[skip ci]` for locale-only updates when appropriate. +- PRs: Include clear description, linked issues (`Fixes #123`), and screenshots/GIFs for UI changes. Add/adjust tests and i18n strings when applicable. +- Quality gates: `npm run lint`, `npm run typecheck`, and relevant tests must pass. Keep PRs focused and small. + +## Security & Configuration Tips +- Secrets: Use `.env` (see `.env_example`); do not commit secrets. +- Backend: Dev server expects ComfyUI backend at `localhost:8188` by default; configure via `.env`. diff --git a/CLAUDE.md b/CLAUDE.md index 5e9b6174c..2e15e3b17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,58 +1,61 @@ -- use `npm run` to see what commands are available -- For component communication, prefer Vue's event-based pattern (emit/@event-name) for state changes and notifications; use defineExpose with refs only for imperative operations that need direct control (like form.validate(), modal.open(), or editor.focus()); events promote loose coupling and are better for reusable components, while exposed methods are acceptable for tightly-coupled component pairs or when wrapping third-party libraries that require imperative APIs -- After making code changes, follow this general process: (1) Create unit tests, component tests, browser tests (if appropriate for each), (2) run unit tests, component tests, and browser tests until passing, (3) run typecheck, lint, format (with prettier) -- you can use `npm run` command to see the scripts available, (4) check if any READMEs (including nested) or documentation needs to be updated, (5) Decide whether the changes are worth adding new content to the external documentation for (or would requires changes to the external documentation) at https://docs.comfy.org, then present your suggestion -- When referencing PrimeVue, you can get all the docs here: https://primevue.org. Do this instead of making up or inferring names of Components -- When trying to set tailwind classes for dark theme, use "dark-theme:" prefix rather than "dark:" -- Never add lines to PR descriptions or commit messages that say "Generated with Claude Code" -- When making PR names and commit messages, if you are going to add a prefix like "docs:", "feat:", "bugfix:", use square brackets around the prefix term and do not use a colon (e.g., should be "[docs]" rather than "docs:"). -- When I reference GitHub Repos related to Comfy-Org, you should proactively fetch or read the associated information in the repo. To do so, you should exhaust all options: (1) Check if we have a local copy of the repo, (2) Use the GitHub API to fetch the information; you may want to do this IN ADDITION to the other options, especially for reading specific branches/PRs/comments/reviews/metadata, and (3) curl the GitHub website and parse the html or json responses -- For information about ComfyUI, ComfyUI_frontend, or ComfyUI-Manager, you can web search or download these wikis: https://deepwiki.com/Comfy-Org/ComfyUI-Manager, https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview, https://deepwiki.com/comfyanonymous/ComfyUI/2-core-architecture -- If a question/project is related to Comfy-Org, Comfy, or ComfyUI ecosystem, you should proactively use the Comfy docs to answer the question. The docs may be referenced with URLs like https://docs.comfy.org -- When operating inside a repo, check for README files at key locations in the repo detailing info about the contents of that folder. E.g., top-level key folders like tests-ui, browser_tests, composables, extensions/core, stores, services often have their own README.md files. When writing code, make sure to frequently reference these README files to understand the overall architecture and design of the project. Pay close attention to the snippets to learn particular patterns that seem to be there for a reason, as you should emulate those. -- Prefer running single tests, and not the whole test suite, for performance -- If using a lesser known or complex CLI tool, run the --help to see the documentation before deciding what to run, even if just for double-checking or verifying things. -- IMPORTANT: the most important goal when writing code is to create clean, best-practices, sustainable, and scalable public APIs and interfaces. Our app is used by thousands of users and we have thousands of mods/extensions that are constantly changing and updating; and we are also always updating. That's why it is IMPORTANT that we design systems and write code that follows practices of domain-driven design, object-oriented design, and design patterns (such that you can assure stability while allowing for all components around you to change and evolve). We ABSOLUTELY prioritize clean APIs and public interfaces that clearly define and restrict how/what the mods/extensions can access. -- If any of these technologies are referenced, you can proactively read their docs at these locations: https://primevue.org/theming, https://primevue.org/forms/, https://www.electronjs.org/docs/latest/api/browser-window, https://vitest.dev/guide/browser/, https://atlassian.design/components/pragmatic-drag-and-drop/core-package/drop-targets/, https://playwright.dev/docs/api/class-test, https://playwright.dev/docs/api/class-electron, https://www.algolia.com/doc/api-reference/rest-api/, https://pyav.org/docs/develop/cookbook/basics.html -- IMPORTANT: Never add Co-Authored by Claude or any reference to Claude or Claude Code in commit messages, PR descriptions, titles, or any documentation whatsoever -- The npm script to type check is called "typecheck" NOT "type check" -- Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API. -- when we are solving an issue we know the link/number for, we should add "Fixes #n" (where n is the issue number) to the PR description. -- Never write css if you can accomplish the same thing with tailwind utility classes -- Utilize ref and reactive for reactive state -- Implement computed properties with computed() -- Use watch and watchEffect for side effects -- Implement lifecycle hooks with onMounted, onUpdated, etc. -- Utilize provide/inject for dependency injection -- Use vue 3.5 style of default prop declaration. Do not define a `props` variable; instead, destructure props. Since vue 3.5, destructuring props does not strip them of reactivity. -- Use Tailwind CSS for styling -- Leverage VueUse functions for performance-enhancing styles -- Use lodash for utility functions -- Implement proper props and emits definitions -- Utilize Vue 3's Teleport component when needed -- Use Suspense for async components -- Implement proper error handling -- Follow Vue 3 style guide and naming conventions -- IMPORTANT: Use vue-i18n for ALL user-facing strings - no hard-coded text in services/utilities. Place new translation entries in src/locales/en/main.json -- Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard. -- DO NOT use deprecated PrimeVue components. Use these replacements instead: - * `Dropdown` โ†’ Use `Select` (import from 'primevue/select') - * `OverlayPanel` โ†’ Use `Popover` (import from 'primevue/popover') - * `Calendar` โ†’ Use `DatePicker` (import from 'primevue/datepicker') - * `InputSwitch` โ†’ Use `ToggleSwitch` (import from 'primevue/toggleswitch') - * `Sidebar` โ†’ Use `Drawer` (import from 'primevue/drawer') - * `Chips` โ†’ Use `AutoComplete` with multiple enabled and typeahead disabled - * `TabMenu` โ†’ Use `Tabs` without panels - * `Steps` โ†’ Use `Stepper` without panels - * `InlineMessage` โ†’ Use `Message` component -* Use `api.apiURL()` for all backend API calls and routes - - Actual API endpoints like /prompt, /queue, /view, etc. - - Image previews: `api.apiURL('/view?...')` - - Any backend-generated content or dynamic routes -* Use `api.fileURL()` for static files served from the public folder: - - Templates: `api.fileURL('/templates/default.json')` - - Extensions: `api.fileURL(extensionPath)` for loading JS modules - - Any static assets that exist in the public directory -- When implementing code that outputs raw HTML (e.g., using v-html directive), always ensure dynamic content has been properly sanitized with DOMPurify or validated through trusted sources. Prefer Vue templates over v-html when possible. -- For any async operations (API calls, timers, etc), implement cleanup/cancellation in component unmount to prevent memory leaks -- Extract complex template conditionals into separate components or computed properties -- Error messages should be actionable and user-friendly (e.g., "Failed to load data. Please refresh the page." instead of "Unknown error") +# ComfyUI Frontend Project Guidelines + +## Quick Commands + +- `npm run`: See all available commands +- `npm run typecheck`: Type checking +- `npm run lint`: Linting +- `npm run format`: Prettier formatting +- `npm run test:component`: Run component tests with browser environment +- `npm run test:unit`: Run all unit tests +- `npm run test:unit -- tests-ui/tests/example.test.ts`: Run single test file + +## Development Workflow + +1. Make code changes +2. Run tests (see subdirectory CLAUDE.md files) +3. Run typecheck, lint, format +4. Check README updates +5. Consider docs.comfy.org updates + +## Git Conventions + +- Use [prefix] format: [feat], [bugfix], [docs] +- Add "Fixes #n" to PR descriptions +- Never mention Claude/AI in commits + +## External Resources + +- PrimeVue docs: +- ComfyUI docs: +- Electron: +- Wiki: + +## Project Philosophy + +- Clean, stable public APIs +- Domain-driven design +- Thousands of users and extensions +- Prioritize clean interfaces that restrict extension access + +## Repository Navigation + +- Check README files in key folders (tests-ui, browser_tests, composables, etc.) +- Prefer running single tests for performance +- Use --help for unfamiliar CLI tools + +## GitHub Integration + +When referencing Comfy-Org repos: + +1. Check for local copy +2. Use GitHub API for branches/PRs/metadata +3. Curl GitHub website if needed + +## Common Pitfalls + +- NEVER use `any` type - use proper TypeScript types +- NEVER use `as any` type assertions - fix the underlying type issue +- NEVER use `--no-verify` flag when committing +- NEVER delete or disable tests to make them pass +- NEVER circumvent quality checks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..ac7164d7b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,333 @@ +# Contributing to ComfyUI Frontend + +We're building this frontend together and would love your help โ€” no matter how you'd like to pitch in! You don't need to write code to make a difference. + +## Ways to Contribute + +- **Pull Requests:** Add features, fix bugs, or improve code health. Browse [issues](https://github.com/Comfy-Org/ComfyUI_frontend/issues) for inspiration. Look for the `Good first issue` label if you're new to the project. +- **Vote on Features:** Give a ๐Ÿ‘ to the feature requests you care about to help us prioritize. +- **Verify Bugs:** Try reproducing reported issues and share your results (even if the bug doesn't occur!). +- **Community Support:** Hop into our [Discord](https://discord.com/invite/comfyorg) to answer questions or get help. +- **Share & Advocate:** Tell your friends, tweet about us, or share tips to support the project. + +Have another idea? Drop into Discord or open an issue, and let's chat! + +## Development Setup + +### Prerequisites & Technology Stack + +- **Required Software**: + - Node.js (v16 or later; v20/v22 strongly recommended) and npm + - Git for version control + - A running ComfyUI backend instance + +- **Tech Stack**: + - [Vue 3.5 Composition API](https://vuejs.org/) with [TypeScript](https://www.typescriptlang.org/) + - [Pinia](https://pinia.vuejs.org/) for state management + - [PrimeVue](https://primevue.org/) with [TailwindCSS](https://tailwindcss.com/) for UI + - litegraph.js (integrated in src/lib) for node editor + - [zod](https://zod.dev/) for schema validation + - [vue-i18n](https://github.com/intlify/vue-i18n) for internationalization + +### Initial Setup + +1. Clone the repository: + ```bash + git clone https://github.com/Comfy-Org/ComfyUI_frontend.git + cd ComfyUI_frontend + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Configure environment (optional): + Create a `.env` file in the project root based on the provided [.env.example](.env.example) file. + + **Note about ports**: By default, the dev server expects the ComfyUI backend at `localhost:8188`. If your ComfyUI instance runs on a different port, update this in your `.env` file. + +### Dev Server Configuration + +To launch ComfyUI and have it connect to your development server: + +```bash +python main.py --port 8188 +``` + +### Git pre-commit hooks + +Run `npm run prepare` to install Git pre-commit hooks. Currently, the pre-commit hook is used to auto-format code on commit. + +### Dev Server + +Note: The dev server will NOT load any extension from the ComfyUI server. Only core extensions will be loaded. + +- Start local ComfyUI backend at `localhost:8188` +- Run `npm run dev` to start the dev server +- Run `npm run dev:electron` to start the dev server with electron API mocked + +#### Access dev server on touch devices + +Enable remote access to the dev server by setting `VITE_REMOTE_DEV` in `.env` to `true`. + +After you start the dev server, you should see following logs: + +``` +> comfyui-frontend@1.3.42 dev +> vite + + + VITE v5.4.6 ready in 488 ms + + โžœ Local: http://localhost:5173/ + โžœ Network: http://172.21.80.1:5173/ + โžœ Network: http://192.168.2.20:5173/ + โžœ press h + enter to show help +``` + +Make sure your desktop machine and touch device are on the same network. On your touch device, +navigate to `http://:5173` (e.g. `http://192.168.2.20:5173` here), to access the ComfyUI frontend. + +## Development Workflow + +### Architecture Decision Records + +We document significant architectural decisions using ADRs (Architecture Decision Records). See [docs/adr/](docs/adr/) for all ADRs and the template for creating new ones. + +### Backporting Changes to Release Branches + +When you fix a bug that affects a version in feature freeze, we use an automated backport process to apply the fix to the release candidate branch. + +#### Real Example + +- Subgraphs feature was released in v1.24 +- While developing v1.25, we discovered a bug in subgraphs +- v1.24 is in feature freeze (only accepting bug fixes, no new features) +- The fix needs to be applied to both main (v1.25) and the v1.24 release candidate + +#### How to Backport Your Fix + +1. Create your PR fixing the bug on `main` branch as usual +2. Before merging, add these labels to your PR: + - `needs-backport` - triggers the automated backport workflow + - `1.24` - targets the `core/1.24` release candidate branch + +3. Merge your PR normally +4. The automated workflow will: + - Create a new branch from `core/1.24` + - Apply your changes to that branch + - Open a new PR to `core/1.24` + - Comment on your original PR with a link to the backport PR + +#### When to Use Backporting + +- Bug fixes for features already released +- Security fixes +- Critical issues affecting existing functionality +- Never for new features (these wait for the next release cycle) + +#### Handling Conflicts + +If the automated cherry-pick fails due to conflicts, the workflow will comment on your PR with: +- The list of conflicting files +- Instructions to manually cherry-pick to the release candidate branch + +See [PR #4616](https://github.com/Comfy-Org/ComfyUI_frontend/pull/4616) for the actual subgraph bugfix that was backported from v1.25 to v1.24. + +## Code Editor Configuration + +### Recommended Setup + +This project includes `.vscode/launch.json.default` and `.vscode/settings.json.default` files with recommended launch and workspace settings for editors that use the `.vscode` directory (e.g., VS Code, Cursor, etc.). + +We've also included a list of recommended extensions in `.vscode/extensions.json`. Your editor should detect this file and show a human friendly list in the Extensions panel, linking each entry to its marketplace page. + +### MCP Integrations + +#### Playwright Browser Automation + +The Playwright MCP server enables LLM coding assistants (like Copilot, Claude Code, etc.) to control and inspect web browsers programmatically. This allows assistants to navigate websites, take screenshots, and interact with web pages on your behalf. + +For ComfyUI_frontend development, you can ask coding assistants to use Playwright screenshots to continuously verify that your code changes produce the expected visual results. + +##### Setup for Claude Code + +After installing dependencies with `npm i`, the Playwright MCP server will be automatically available when you start Claude Code locally. + +Here's how Claude Code can use the Playwright MCP server to inspect the interface of the local development server (assuming you're running the dev server at `localhost:5173`): + +``` +> navigate to localhost:5173 and take a screenshot. + tell me the items in the menubar + +โ— I'll navigate to localhost:5173 and take a + screenshot to see the menubar items. + +โ— playwright:playwright_navigate (MCP)(url: โ€ฆ + "http://local + host:5173") + โŽฟ Navigated to http://localhost:5173 + +โ— playwright:playwright_screenshot (MCP)(name: "loโ€ฆ + alhost_5173 + _menubar") + + โŽฟ Screenshot saved to: ../../../Downloads/localho + st_5173_menubar-2025-05-30T17-23-00-942Z.png + โŽฟ Screenshot also stored in memory with name: + 'localhost_5173_menubar' + +โ— Read(../../../Downloads/localhost_5173_menubar-2โ€ฆ + 25-05-30T17-23-00-942Z.png) + โŽฟ Read image (94.3KB) + +โ— Based on the screenshot, I can see the menubar at + the top of the ComfyUI interface contains the + following items: + + Main Menubar Items: + - Workflow - Menu for workflow-related actions + - Edit - Menu for editing operations + - Help - Menu for help and documentation + + Additional UI Elements: + - ComfyUI Logo - On the far left + - Workflow Tab - Shows "Unsaved Workflow" with a + dropdown and close button + - Layout Controls - On the far right (grid view + and hamburger menu icons) + + The interface shows a typical ComfyUI workflow + graph with nodes like "Load Checkpoint", "CLIP + Text Encode (Prompt)", "KSampler", and "Empty + Latent Image" connected with colored cables. +``` + +## Testing + +### Unit Tests + +- `npm i` to install all dependencies +- `npm run test:unit` to execute all unit tests + +### Component Tests + +Component tests verify Vue components in `src/components/`. + +- `npm run test:component` to execute all component tests + +### Playwright Tests + +Playwright tests verify the whole app. See [browser_tests/README.md](browser_tests/README.md) for details. + +### Running All Tests + +Before submitting a PR, ensure all tests pass: + +```bash +npm run test:unit +npm run test:component +npm run test:browser +npm run typecheck +npm run lint +npm run format +``` + +## Code Style Guidelines + +### TypeScript +- Use TypeScript for all new code +- Avoid `any` types - use proper type definitions +- Never use `@ts-expect-error` - fix the underlying type issue + +### Vue 3 Patterns +- Use Composition API for all components +- Follow Vue 3.5+ patterns (props destructuring is reactive) +- Use ` +``` + +### Backend Access Patterns + +```python +# Check if a specific client supports a feature +if feature_flags.supports_feature( + sockets_metadata, + client_id, + "supports_preview_metadata" +): + # Client supports this feature + +# Get feature value with default +max_size = feature_flags.get_connection_feature( + sockets_metadata, + client_id, + "max_upload_size", + 100 * 1024 * 1024 # Default 100MB +) +``` + +## Adding New Feature Flags + +### Backend + +1. **For server capabilities**, add to `SERVER_FEATURE_FLAGS` in `comfy_api/feature_flags.py`: +```python +SERVER_FEATURE_FLAGS = { + "supports_preview_metadata": True, + "max_upload_size": args.max_upload_size * 1024 * 1024, + "your_new_feature": True, # Add your flag +} +``` + +2. **Use in your code:** +```python +if feature_flags.supports_feature(sockets_metadata, sid, "your_new_feature"): + # Feature-specific code +``` + +### Frontend + +1. **For client capabilities**, add to `src/config/clientFeatureFlags.json`: +```json +{ + "supports_preview_metadata": false, + "your_new_feature": true +} +``` + +2. **For extension features**, update the composable to add convenience accessors: +```typescript +// In useFeatureFlags.ts +const extension = { + manager: { + supportsV4: computed(() => getServerFeature('extension.manager.supports_v4', false)) + }, + yourExtension: { + supportsNewFeature: computed(() => getServerFeature('extension.yourExtension.supports_new_feature', false)) + } +} + +return { + // ... existing returns + extension +} +``` + +## Testing Feature Flags + +```mermaid +graph LR + A[Test Scenarios] --> B[Both support feature] + A --> C[Only frontend supports] + A --> D[Only backend supports] + A --> E[Neither supports] + + B --> F[Feature enabled] + C --> G[Feature disabled] + D --> H[Feature disabled] + E --> I[Feature disabled] +``` + +Test your feature flags with different combinations: +- Frontend with flag + Backend with flag = Feature works +- Frontend with flag + Backend without = Graceful degradation +- Frontend without + Backend with flag = No feature usage +- Neither has flag = Default behavior + +### Example Test + +```typescript +// In tests-ui/tests/api.featureFlags.test.ts +it('should handle preview metadata based on feature flag', () => { + // Mock server supports feature + api.serverFeatureFlags = { supports_preview_metadata: true } + + expect(api.serverSupportsFeature('supports_preview_metadata')).toBe(true) + + // Mock server doesn't support feature + api.serverFeatureFlags = {} + + expect(api.serverSupportsFeature('supports_preview_metadata')).toBe(false) +}) \ No newline at end of file diff --git a/docs/adr/0001-merge-litegraph-into-frontend.md b/docs/adr/0001-merge-litegraph-into-frontend.md new file mode 100644 index 000000000..714a7e9dc --- /dev/null +++ b/docs/adr/0001-merge-litegraph-into-frontend.md @@ -0,0 +1,65 @@ +# 1. Merge LiteGraph.js into ComfyUI Frontend + +Date: 2025-08-05 + +## Status + +Accepted + +## Context + +ComfyUI's frontend architecture currently depends on a forked version of litegraph.js maintained as a separate package (@comfyorg/litegraph). This separation has created several architectural and operational challenges: + +**Architectural Issues:** +- The current split creates a distributed monolith where both packages handle rendering, user interactions, and data models without clear separation of responsibilities +- Both frontend and litegraph manipulate the same data structures, forcing tight coupling across the frontend's data model, views, and business logic +- The lack of clear boundaries prevents implementation of modern architectural patterns like MVC or event-sourcing + +**Operational Issues:** +- ComfyUI is the only known user of the @comfyorg/litegraph fork +- Managing separate repositories significantly slows developer velocity due to coordination overhead +- Version mismatches between frontend and litegraph cause recurring issues +- No upstream contributions to consider (original litegraph.js is no longer maintained) + +**Future Requirements:** +The following planned features are blocked by the current architecture: +- Multiplayer collaboration requiring CRDT-based state management +- Cloud-based backend support +- Alternative rendering backends +- Improved undo/redo system +- Clear API versioning and compatibility layers + +## Decision + +We will merge litegraph.js directly into the ComfyUI frontend repository using git subtree to preserve the complete commit history. + +The merge will: +1. Move litegraph source to `src/lib/litegraph/` +2. Update all import paths from `@comfyorg/litegraph` to `@/lib/litegraph` +3. Remove the npm dependency on `@comfyorg/litegraph` +4. Preserve the full git history using subtree merge + +This integration is the first step toward restructuring the application along clear Model-View-Controller boundaries, with state mutations going through a single CRDT-mediated access point. + +## Consequences + +### Positive + +- **Enables architectural refactoring**: Direct integration allows restructuring along proper MVC boundaries +- **Unblocks new features**: Multiplayer, cloud features, and improved undo/redo can now be implemented +- **Faster development**: Eliminates overhead of coordinating changes across two tightly-coupled packages +- **Better developer experience**: No more version mismatch issues or cross-repository debugging +- **Simplified maintenance**: One less repository to maintain, release, and version + +### Negative + +- **Larger repository**: The frontend repository will increase in size +- **Loss of versioning**: No more semantic versioning for litegraph changes +- **Maintenance responsibility**: Must maintain litegraph code directly +- **Historical references**: Past commit messages may reference issues from the original litegraph repository + +## Notes + +- Git subtree was chosen over submodules to provide a cleaner developer experience +- The original litegraph repository will be archived after the merge +- Future litegraph improvements will be made directly in the frontend repository \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 000000000..67ef529de --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,79 @@ +# Architecture Decision Records + +This directory contains Architecture Decision Records (ADRs) for the ComfyUI Frontend project. + +## What is an ADR? + +An Architecture Decision Record captures an important architectural decision made along with its context and consequences. ADRs help future developers understand why certain decisions were made and provide a historical record of the project's evolution. + +## ADR Index + +| ADR | Title | Status | Date | +|-----|-------|--------|------| +| [0001](0001-merge-litegraph-into-frontend.md) | Merge LiteGraph.js into ComfyUI Frontend | Accepted | 2025-08-05 | + +## Creating a New ADR + +1. Copy the template below +2. Name it with the next number in sequence: `NNNN-descriptive-title.md` +3. Fill in all sections +4. Update this index +5. Submit as part of your PR + +## ADR Template + +```markdown +# N. Title + +Date: YYYY-MM-DD + +## Status + +[Proposed | Accepted | Rejected | Deprecated | Superseded by [ADR-NNNN](NNNN-title.md)] + +## Context + +Describe the issue that motivated this decision and any context that influences or constrains the decision. + +- What is the problem? +- Why does it need to be solved? +- What forces are at play (technical, business, team)? + +## Decision + +Describe the decision that was made and the key points that led to it. + +- What are we going to do? +- How will we do it? +- What alternatives were considered? + +## Consequences + +### Positive + +- What becomes easier or better? +- What opportunities does this create? + +### Negative + +- What becomes harder or worse? +- What risks are we accepting? +- What technical debt might we incur? + +## Notes + +Optional section for additional information, references, or clarifications. +``` + +## ADR Status Values + +- **Proposed**: The decision is being discussed +- **Accepted**: The decision has been agreed upon +- **Rejected**: The decision was not accepted +- **Deprecated**: The decision is no longer relevant +- **Superseded**: The decision has been replaced by another ADR + +## Further Reading + +- [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) by Michael Nygard +- [Architecture Decision Records](https://adr.github.io/) - Collection of ADR resources \ No newline at end of file diff --git a/knip.config.ts b/knip.config.ts new file mode 100644 index 000000000..4d102262a --- /dev/null +++ b/knip.config.ts @@ -0,0 +1,75 @@ +import type { KnipConfig } from 'knip' + +const config: KnipConfig = { + entry: [ + 'src/main.ts', + 'vite.config.mts', + 'vite.electron.config.mts', + 'vite.types.config.mts', + 'eslint.config.js', + 'tailwind.config.js', + 'postcss.config.js', + 'playwright.config.ts', + 'playwright.i18n.config.ts', + 'vitest.config.ts', + 'scripts/**/*.{js,ts}' + ], + project: [ + 'src/**/*.{js,ts,vue}', + 'tests-ui/**/*.{js,ts,vue}', + 'browser_tests/**/*.{js,ts}', + 'scripts/**/*.{js,ts}' + ], + ignore: [ + // Generated files + 'dist/**', + 'types/**', + 'node_modules/**', + // Config files that might not show direct usage + '.husky/**', + // Temporary or cache files + '.vite/**', + 'coverage/**', + // i18n config + '.i18nrc.cjs', + // Test setup files + 'browser_tests/globalSetup.ts', + 'browser_tests/globalTeardown.ts', + 'browser_tests/utils/**', + // Scripts + 'scripts/**', + // Vite config files + 'vite.electron.config.mts', + 'vite.types.config.mts', + // Auto generated manager types + 'src/types/generatedManagerTypes.ts' + ], + ignoreExportsUsedInFile: true, + // Vue-specific configuration + vue: true, + // Only check for unused files, disable all other rules + // TODO: Gradually enable other rules - see https://github.com/Comfy-Org/ComfyUI_frontend/issues/4888 + rules: { + binaries: 'off', + classMembers: 'off', + dependencies: 'off', + devDependencies: 'off', + duplicates: 'off', + enumMembers: 'off', + exports: 'off', + nsExports: 'off', + nsTypes: 'off', + types: 'off', + unlisted: 'off' + }, + // Include dependencies analysis + includeEntryExports: true, + // Workspace configuration for monorepo-like structure + workspaces: { + '.': { + entry: ['src/main.ts'] + } + } +} + +export default config diff --git a/package-lock.json b/package-lock.json index fa5cee570..135a1d41d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "@comfyorg/comfyui-frontend", - "version": "1.24.1", + "version": "1.26.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@comfyorg/comfyui-frontend", - "version": "1.24.1", + "version": "1.26.1", "license": "GPL-3.0-only", "dependencies": { "@alloc/quick-lru": "^5.2.0", "@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@comfyorg/comfyui-electron-types": "^0.4.43", - "@comfyorg/litegraph": "^0.16.9", "@primevue/forms": "^4.2.5", "@primevue/themes": "^4.2.5", "@sentry/vue": "^8.48.0", @@ -31,6 +30,8 @@ "axios": "^1.8.2", "dompurify": "^3.2.5", "dotenv": "^16.4.5", + "extendable-media-recorder": "^9.2.27", + "extendable-media-recorder-wav-encoder": "^7.0.129", "firebase": "^11.6.0", "fuse.js": "^7.0.0", "jsondiffpatch": "^0.6.0", @@ -40,6 +41,7 @@ "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.2.5", + "semver": "^7.7.2", "three": "^0.170.0", "tiptap-markdown": "^0.8.10", "vue": "^3.5.13", @@ -62,6 +64,7 @@ "@types/fs-extra": "^11.0.4", "@types/lodash": "^4.17.6", "@types/node": "^20.14.8", + "@types/semver": "^7.7.0", "@types/three": "^0.169.0", "@vitejs/plugin-vue": "^5.1.4", "@vue/test-utils": "^2.4.6", @@ -77,6 +80,7 @@ "happy-dom": "^15.11.0", "husky": "^9.0.11", "identity-obj-proxy": "^3.0.0", + "knip": "^5.62.0", "lint-staged": "^15.2.7", "postcss": "^8.4.39", "prettier": "^3.3.2", @@ -84,8 +88,8 @@ "tsx": "^4.15.6", "typescript": "^5.4.5", "typescript-eslint": "^8.0.0", - "unplugin-icons": "^0.19.3", - "unplugin-vue-components": "^0.27.4", + "unplugin-icons": "^0.22.0", + "unplugin-vue-components": "^0.28.0", "vite": "^5.4.19", "vite-plugin-dts": "^4.3.0", "vite-plugin-html": "^3.2.2", @@ -437,13 +441,13 @@ } }, "node_modules/@antfu/install-pkg": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", - "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.5.0.tgz", + "integrity": "sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==", "dev": true, "dependencies": { - "package-manager-detector": "^0.2.0", - "tinyexec": "^0.3.0" + "package-manager-detector": "^0.2.5", + "tinyexec": "^0.3.1" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -557,6 +561,15 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", @@ -601,6 +614,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", @@ -622,6 +644,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", @@ -877,13 +908,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } @@ -948,12 +976,6 @@ "integrity": "sha512-o6WFbYn9yAkGbkOwvhPF7pbKDvN0occZ21Tfyhya8CIsIqKpTHLft0aOqo4yhSh+kTxN16FYjsfrTH5Olk4WuA==", "license": "GPL-3.0-only" }, - "node_modules/@comfyorg/litegraph": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.16.9.tgz", - "integrity": "sha512-ZsvqkLqdG65e2UyM8oTOUTv/7VFEyGbG/C9dCZnhxdNq30UaE+F0iLaKq/17u6w4yewyZuqIn5MoOtjpxPqLDQ==", - "license": "MIT" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -980,6 +1002,40 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -2301,20 +2357,112 @@ "dev": true }, "node_modules/@iconify/utils": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.32.tgz", - "integrity": "sha512-LeifFZPPKu28O3AEDpYJNdEbvS4/ojAPyIW+pF/vUpJTYnbTiXUHkCh0bwgFRzKvdpb8H4Fbfd/742++MF4fPQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", "dev": true, "dependencies": { - "@antfu/install-pkg": "^0.4.0", - "@antfu/utils": "^0.7.10", + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", - "debug": "^4.3.6", + "debug": "^4.4.0", + "globals": "^15.14.0", "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "mlly": "^1.7.1" + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" } }, + "node_modules/@iconify/utils/node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@iconify/utils/node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@iconify/utils/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true + }, + "node_modules/@iconify/utils/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@iconify/utils/node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "dev": true, + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@iconify/utils/node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "dev": true + }, + "node_modules/@iconify/utils/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@iconify/utils/node_modules/pkg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", + "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", + "dev": true, + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/@iconify/utils/node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true + }, "node_modules/@inkjs/ui": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@inkjs/ui/-/ui-1.0.0.tgz", @@ -2422,18 +2570,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/synckit": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", @@ -2968,6 +3104,19 @@ "node": ">=18" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", + "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3019,6 +3168,275 @@ "node": ">=8.0.0" } }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.6.1.tgz", + "integrity": "sha512-Ma/kg29QJX1Jzelv0Q/j2iFuUad1WnjgPjpThvjqPjpOyLjCUaiFCCnshhmWjyS51Ki1Iol3fjf1qAzObf8GIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.6.1.tgz", + "integrity": "sha512-xjL/FKKc5p8JkFWiH7pJWSzsewif3fRf1rw2qiRxRvq1uIa6l7Zoa14Zq2TNWEsqDjdeOrlJtfWiPNRnevK0oQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.6.1.tgz", + "integrity": "sha512-u0yrJ3NHE0zyCjiYpIyz4Vmov21MA0yFKbhHgixDU/G6R6nvC8ZpuSFql3+7C8ttAK9p8WpqOGweepfcilH5Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.6.1.tgz", + "integrity": "sha512-2lox165h1EhzxcC8edUy0znXC/hnAbUPaMpYKVlzLpB2AoYmgU4/pmofFApj+axm2FXpNamjcppld8EoHo06rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.6.1.tgz", + "integrity": "sha512-F45MhEQ7QbHfsvZtVNuA/9obu3il7QhpXYmCMfxn7Zt9nfAOw4pQ8hlS5DroHVp3rW35u9F7x0sixk/QEAi3qQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.6.1.tgz", + "integrity": "sha512-r+3+MTTl0tD4NoWbfTIItAxJvuyIU7V0fwPDXrv7Uj64vZ3OYaiyV+lVaeU89Bk/FUUQxeUpWBwdKNKHjyRNQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.6.1.tgz", + "integrity": "sha512-TBTZ63otsWZ72Z8ZNK2JVS0HW1w9zgOixJTFDNrYPUUW1pXGa28KAjQ1yGawj242WLAdu3lwdNIWtkxeO2BLxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.6.1.tgz", + "integrity": "sha512-SjwhNynjSG2yMdyA0f7wz7Yvo3ppejO+ET7n2oiI7ApCXrwxMzeRWjBzQt+oVWr2HzVOfaEcDS9rMtnR83ulig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.6.1.tgz", + "integrity": "sha512-f4EMidK6rosInBzPMnJ0Ri4RttFCvvLNUNDFUBtELW/MFkBwPTDlvbsmW0u0Mk/ruBQ2WmRfOZ6tT62kWMcX2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.6.1.tgz", + "integrity": "sha512-1umENVKeUsrWnf5IlF/6SM7DCv8G6CoKI2LnYR6qhZuLYDPS4PBZ0Jow3UDV9Rtbv5KRPcA3/uXjI88ntWIcOQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.6.1.tgz", + "integrity": "sha512-Hjyp1FRdJhsEpIxsZq5VcDuFc8abC0Bgy8DWEa31trCKoTz7JqA7x3E2dkFbrAKsEFmZZ0NvuG5Ip3oIRARhow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.6.1.tgz", + "integrity": "sha512-ODJOJng6f3QxpAXhLel3kyWs8rPsJeo9XIZHzA7p//e+5kLMDU7bTVk4eZnUHuxsqsB8MEvPCicJkKCEuur5Ag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.6.1.tgz", + "integrity": "sha512-hCzRiLhqe1ZOpHTsTGKp7gnMJRORlbCthawBueer2u22RVAka74pV/+4pP1tqM07mSlQn7VATuWaDw9gCl+cVg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.6.1.tgz", + "integrity": "sha512-JansPD8ftOzMYIC3NfXJ68tt63LEcIAx44Blx6BAd7eY880KX7A0KN3hluCrelCz5aQkPaD95g8HBiJmKaEi2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.6.1.tgz", + "integrity": "sha512-R78ES1rd4z2x5NrFPtSWb/ViR1B8wdl+QN2X8DdtoYcqZE/4tvWtn9ZTCXMEzUp23tchJ2wUB+p6hXoonkyLpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.6.1.tgz", + "integrity": "sha512-qAR3tYIf3afkij/XYunZtlz3OH2Y4ni10etmCFIJB5VRGsqJyI6Hl+2dXHHGJNwbwjXjSEH/KWJBpVroF3TxBw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.6.1.tgz", + "integrity": "sha512-QqygWygIuemGkaBA48POOTeinbVvlamqh6ucm8arGDGz/mB5O00gXWxed12/uVrYEjeqbMkla/CuL3fjL3EKvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.6.1.tgz", + "integrity": "sha512-N2+kkWwt/bk0JTCxhPuK8t8JMp3nd0n2OhwOkU8KO4a7roAJEa4K1SZVjMv5CqUIr5sx2CxtXRBoFDiORX5oBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.6.1.tgz", + "integrity": "sha512-DfMg3cU9bJUbN62Prbp4fGCtLgexuwyEaQGtZAp8xmi1Ii26uflOGx0FJkFTF6lVMSFoIRFvIL8gsw5/ZdHrMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pinia/testing": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.5.tgz", @@ -4385,6 +4803,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -4523,6 +4952,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true + }, "node_modules/@types/stats.js": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", @@ -4754,19 +5189,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0.tgz", @@ -5724,6 +6146,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/automation-events": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.11.tgz", + "integrity": "sha512-TnclbJ0482ydRenzrR9FIbqalHScBBdQTIXv8tVunhYx8dq7E0Eq5v5CSAo67YmLXNbx5jCstHcLZDJ33iONDw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -5974,6 +6409,18 @@ "node": ">=8" } }, + "node_modules/broker-factory": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.7.tgz", + "integrity": "sha512-RxbMXWq/Qvw9aLZMvuooMtVTm2/SV9JEpxpBbMuFhYAnDaZxctbJ+1b9ucHxADk/eQNqDijvWQjLVARqExAeyg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "fast-unique-numbers": "^9.0.22", + "tslib": "^2.8.1", + "worker-factory": "^7.0.43" + } + }, "node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -6537,23 +6984,10 @@ "dev": true, "license": "MIT" }, - "node_modules/conf/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true }, "node_modules/config-chain": { @@ -7449,19 +7883,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/editorconfig/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7741,18 +8162,6 @@ "eslint": ">=6.0.0" } }, - "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-config-prettier": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", @@ -7852,19 +8261,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-vue/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8310,6 +8706,12 @@ "node": ">= 0.6" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8330,6 +8732,56 @@ "node": ">=0.10.0" } }, + "node_modules/extendable-media-recorder": { + "version": "9.2.27", + "resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-9.2.27.tgz", + "integrity": "sha512-2X+Ixi1cxLek0Cj9x9atmhQ+apG+LwJpP2p3ypP8Pxau0poDnicrg7FTfPVQV5PW/3DHFm/eQ16vbgo5Yk3HGQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "media-encoder-host": "^9.0.20", + "multi-buffer-data-view": "^6.0.22", + "recorder-audio-worklet": "^6.0.48", + "standardized-audio-context": "^25.3.77", + "subscribable-things": "^2.1.53", + "tslib": "^2.8.1" + } + }, + "node_modules/extendable-media-recorder-wav-encoder": { + "version": "7.0.129", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.129.tgz", + "integrity": "sha512-/wqM2hnzvLy/iUlg/EU3JIF8MJcidy8I77Z7CCm5+CVEClDfcs6bH9PgghuisndwKTaud0Dh48RTD83gkfEjCw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "extendable-media-recorder-wav-encoder-broker": "^7.0.119", + "extendable-media-recorder-wav-encoder-worker": "^8.0.116", + "tslib": "^2.8.1" + } + }, + "node_modules/extendable-media-recorder-wav-encoder-broker": { + "version": "7.0.119", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.119.tgz", + "integrity": "sha512-BLrFOnqFLpsmmNpSk/TfjNs4j6ImCSGtoryIpRlqNu5S/Avt6gRJI0s4UYvdK7h17PCi+8vaDr75blvmU1sYlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "broker-factory": "^3.1.7", + "extendable-media-recorder-wav-encoder-worker": "^8.0.116", + "tslib": "^2.8.1" + } + }, + "node_modules/extendable-media-recorder-wav-encoder-worker": { + "version": "8.0.116", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.116.tgz", + "integrity": "sha512-bJPR0B7ZHeoqi9YoSie+UXAfEYya3efQ9eLiWuyK4KcOv+SuYQvWCoyzX5kjvb6GqIBCUnev5xulfeHRlyCwvw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1", + "worker-factory": "^7.0.43" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8345,16 +8797,17 @@ "license": "Apache-2.0" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -8373,6 +8826,19 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-unique-numbers": { + "version": "9.0.22", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.22.tgz", + "integrity": "sha512-dBR+30yHAqBGvOuxxQdnn2lTLHCO6r/9B+M4yF8mNrzr3u1yiF+YVJ6u3GTyPN/VRWqaE1FcscZDdBgVKmrmQQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/fast-uri": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", @@ -8414,6 +8880,16 @@ "node": ">=0.8.0" } }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -8709,6 +9185,22 @@ "node": ">=0.4.x" } }, + "node_modules/formatly": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.2.4.tgz", + "integrity": "sha512-lIN7GpcvX/l/i24r/L9bnJ0I8Qn01qijWpQpDDvTLL29nKqSaJJu4h20+7VJ6m2CAhQ2/En/GbxDiHCzq/0MyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", @@ -8982,11 +9474,10 @@ } }, "node_modules/globals": { - "version": "15.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", - "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -10284,18 +10775,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/jsonc-eslint-parser/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jsondiffpatch": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", @@ -10393,6 +10872,109 @@ "node": ">=0.10.0" } }, + "node_modules/knip": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.62.0.tgz", + "integrity": "sha512-hfTUVzmrMNMT1khlZfAYmBABeehwWUUrizLQoLamoRhSFkygsGIXWx31kaWKBgEaIVL77T3Uz7IxGvSw+CvQ6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + }, + { + "type": "polar", + "url": "https://polar.sh/webpro-nl" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "fast-glob": "^3.3.3", + "formatly": "^0.2.4", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "minimist": "^1.2.8", + "oxc-resolver": "^11.1.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.1", + "smol-toml": "^1.3.4", + "strip-json-comments": "5.0.2", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4" + } + }, + "node_modules/knip/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/knip/node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/knip/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/knip/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", + "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -10726,19 +11308,6 @@ "node": ">=14" } }, - "node_modules/langsmith/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/latest-version": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", @@ -10863,13 +11432,13 @@ } }, "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", "dev": true, "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" }, "engines": { "node": ">=14" @@ -11408,6 +11977,43 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/media-encoder-host": { + "version": "9.0.20", + "resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-9.0.20.tgz", + "integrity": "sha512-IyEYxw6az97RNuETOAZV4YZqNAPOiF9GKIp5mVZb4HOyWd6mhkWQ34ydOzhqAWogMyc4W05kjN/VCgTtgyFmsw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "media-encoder-host-broker": "^8.0.19", + "media-encoder-host-worker": "^10.0.19", + "tslib": "^2.8.1" + } + }, + "node_modules/media-encoder-host-broker": { + "version": "8.0.19", + "resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-8.0.19.tgz", + "integrity": "sha512-lTpsNuaZdTCdtTHsOyww7Ae0Mwv+7mFS+O4YkFYWhXwVs0rm6XbRK5jRRn5JmcX3n1eTE1lQS5RgX8qbNaIjSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "broker-factory": "^3.1.7", + "fast-unique-numbers": "^9.0.22", + "media-encoder-host-worker": "^10.0.19", + "tslib": "^2.8.1" + } + }, + "node_modules/media-encoder-host-worker": { + "version": "10.0.19", + "resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-10.0.19.tgz", + "integrity": "sha512-I8fwc6f41peER3RFSiwDxnIHbqU7p3pc2ghQozcw9CQfL0mWEo4IjQJtyswrrlL/HO2pgVSMQbaNzE4q/0mfDQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "extendable-media-recorder-wav-encoder-broker": "^7.0.119", + "tslib": "^2.8.1", + "worker-factory": "^7.0.43" + } + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -12139,17 +12745,23 @@ "dev": true }, "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", "dev": true, "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" } }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -12182,6 +12794,19 @@ "dev": true, "license": "MIT" }, + "node_modules/multi-buffer-data-view": { + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-6.0.22.tgz", + "integrity": "sha512-SsI/exkodHsh+ofCV7An2PZWRaJC7eFVl7gtHQlMWFEDmWtb7cELr/GK32Nhe/6dZQhbr81o+Moswx9aXN3RRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -12221,6 +12846,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -12574,6 +13215,41 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "license": "MIT" }, + "node_modules/oxc-resolver": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.6.1.tgz", + "integrity": "sha512-WQgmxevT4cM5MZ9ioQnEwJiHpPzbvntV5nInGAKo9NQZzegcOonHvcVcnkYqld7bTG35UFHEKeF7VwwsmA3cZg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.6.1", + "@oxc-resolver/binding-android-arm64": "11.6.1", + "@oxc-resolver/binding-darwin-arm64": "11.6.1", + "@oxc-resolver/binding-darwin-x64": "11.6.1", + "@oxc-resolver/binding-freebsd-x64": "11.6.1", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.6.1", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.6.1", + "@oxc-resolver/binding-linux-arm64-gnu": "11.6.1", + "@oxc-resolver/binding-linux-arm64-musl": "11.6.1", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.6.1", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.6.1", + "@oxc-resolver/binding-linux-riscv64-musl": "11.6.1", + "@oxc-resolver/binding-linux-s390x-gnu": "11.6.1", + "@oxc-resolver/binding-linux-x64-gnu": "11.6.1", + "@oxc-resolver/binding-linux-x64-musl": "11.6.1", + "@oxc-resolver/binding-wasm32-wasi": "11.6.1", + "@oxc-resolver/binding-win32-arm64-msvc": "11.6.1", + "@oxc-resolver/binding-win32-ia32-msvc": "11.6.1", + "@oxc-resolver/binding-win32-x64-msvc": "11.6.1" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -12688,24 +13364,14 @@ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "dev": true }, - "node_modules/package-json/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/package-manager-detector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz", - "integrity": "sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==", - "dev": true + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "dependencies": { + "quansync": "^0.2.7" + } }, "node_modules/pako": { "version": "1.0.11", @@ -12999,16 +13665,22 @@ } }, "node_modules/pkg-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", - "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.1", - "pathe": "^1.1.2" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, "node_modules/playwright": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", @@ -13597,6 +14269,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -13754,10 +14442,31 @@ "node": ">=8.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "node_modules/recorder-audio-worklet": { + "version": "6.0.48", + "resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-6.0.48.tgz", + "integrity": "sha512-PVlq/1hjCrPcUGqARg8rR30A303xDCao0jmlBTaUaKkN3Xme58RI7EQxurv8rw2eDwVrN+nrni0UoJoa5/v+zg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "broker-factory": "^3.1.7", + "fast-unique-numbers": "^9.0.22", + "recorder-audio-worklet-processor": "^5.0.35", + "standardized-audio-context": "^25.3.77", + "subscribable-things": "^2.1.53", + "tslib": "^2.8.1", + "worker-factory": "^7.0.43" + } + }, + "node_modules/recorder-audio-worklet-processor": { + "version": "5.0.35", + "resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-5.0.35.tgz", + "integrity": "sha512-5Nzbk/6QzC3QFQ1EG2SE34c1ygLE22lIOvLyjy7N6XxE/jpAZrL4e7xR+yihiTaG3ajiWy6UjqL4XEBMM9ahFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1" + } }, "node_modules/registry-auth-token": { "version": "5.0.3", @@ -14274,6 +14983,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs-interop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz", + "integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==", + "license": "MIT" + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -14343,12 +15058,14 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -14596,6 +15313,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smol-toml": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.2.tgz", + "integrity": "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14666,6 +15396,17 @@ "dev": true, "license": "MIT" }, + "node_modules/standardized-audio-context": { + "version": "25.3.77", + "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz", + "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "automation-events": "^7.0.9", + "tslib": "^2.7.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -14845,6 +15586,17 @@ "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==", "dev": true }, + "node_modules/subscribable-things": { + "version": "2.1.53", + "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.53.tgz", + "integrity": "sha512-zWvN9F/eYQWDKszXl4NXkyqPXvMDZDmXfcHiM5C5WQZTTY2OK+2TZeDlA9oio69FEPqPu9T6yeEcAhQ2uRmnaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "rxjs-interop": "^2.0.0", + "tslib": "^2.8.1" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -15096,8 +15848,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/tinypool": { "version": "1.0.1", @@ -16029,39 +16780,32 @@ } }, "node_modules/unplugin": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.13.1.tgz", - "integrity": "sha512-6Kq1iSSwg7KyjcThRUks9LuqDAKvtnioxbL9iEtB9ctTyBA5OmrB8gZd/d225VJu1w3UpUsKV7eGrvf59J7+VA==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.5.tgz", + "integrity": "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==", "dev": true, "dependencies": { - "acorn": "^8.12.1", + "acorn": "^8.14.1", + "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } + "node": ">=18.12.0" } }, "node_modules/unplugin-icons": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.19.3.tgz", - "integrity": "sha512-EUegRmsAI6+rrYr0vXjFlIP+lg4fSC4zb62zAZKx8FGXlWAGgEGBCa3JDe27aRAXhistObLPbBPhwa/0jYLFkQ==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.22.0.tgz", + "integrity": "sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==", "dev": true, "dependencies": { - "@antfu/install-pkg": "^0.4.1", + "@antfu/install-pkg": "^0.5.0", "@antfu/utils": "^0.7.10", - "@iconify/utils": "^2.1.29", - "debug": "^4.3.6", + "@iconify/utils": "^2.2.0", + "debug": "^4.4.0", "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "unplugin": "^1.12.0" + "local-pkg": "^0.5.1", + "unplugin": "^2.1.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -16070,6 +16814,7 @@ "@svgr/core": ">=7.0.0", "@svgx/core": "^1.0.1", "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", "vue-template-compiler": "^2.6.12", "vue-template-es2015-compiler": "^1.9.0" }, @@ -16083,6 +16828,9 @@ "@vue/compiler-sfc": { "optional": true }, + "svelte": { + "optional": true + }, "vue-template-compiler": { "optional": true }, @@ -16091,22 +16839,39 @@ } } }, + "node_modules/unplugin-icons/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/unplugin-vue-components": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz", - "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.28.0.tgz", + "integrity": "sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==", "dev": true, "dependencies": { "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.0", + "@rollup/pluginutils": "^5.1.4", "chokidar": "^3.6.0", - "debug": "^4.3.6", + "debug": "^4.4.0", "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.11", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.15", "minimatch": "^9.0.5", - "mlly": "^1.7.1", - "unplugin": "^1.12.1" + "mlly": "^1.7.3", + "unplugin": "^2.1.0" }, "engines": { "node": ">=14" @@ -16137,6 +16902,23 @@ "balanced-match": "^1.0.0" } }, + "node_modules/unplugin-vue-components/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/unplugin-vue-components/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -16152,6 +16934,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/unplugin/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -16207,19 +17001,6 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -17039,19 +17820,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/vue-i18n": { "version": "9.14.3", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.3.tgz", @@ -17104,19 +17872,6 @@ "typescript": ">=5.0.0" } }, - "node_modules/vue-tsc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/vuefire": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/vuefire/-/vuefire-3.2.1.tgz", @@ -17167,6 +17922,16 @@ "node": ">=14" } }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -17374,6 +18139,17 @@ "node": ">=0.10.0" } }, + "node_modules/worker-factory": { + "version": "7.0.43", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.43.tgz", + "integrity": "sha512-SACVoj3gWKtMVyT9N+VD11Pd/Xe58+ZFfp8b7y/PagOvj3i8lU3Uyj+Lj7WYTmSBvNLC0JFaQkx44E6DhH5+WA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "fast-unique-numbers": "^9.0.22", + "tslib": "^2.8.1" + } + }, "node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", diff --git a/package.json b/package.json index 2dfd9aa87..27cc90d77 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.24.1", + "version": "1.26.1", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", @@ -23,6 +23,7 @@ "preview": "vite preview", "lint": "eslint src", "lint:fix": "eslint src --fix", + "knip": "knip", "locale": "lobe-i18n locale", "collect-i18n": "playwright test --config=playwright.i18n.config.ts", "json-schema": "tsx scripts/generate-json-schema.ts" @@ -40,6 +41,7 @@ "@types/fs-extra": "^11.0.4", "@types/lodash": "^4.17.6", "@types/node": "^20.14.8", + "@types/semver": "^7.7.0", "@types/three": "^0.169.0", "@vitejs/plugin-vue": "^5.1.4", "@vue/test-utils": "^2.4.6", @@ -55,6 +57,7 @@ "happy-dom": "^15.11.0", "husky": "^9.0.11", "identity-obj-proxy": "^3.0.0", + "knip": "^5.62.0", "lint-staged": "^15.2.7", "postcss": "^8.4.39", "prettier": "^3.3.2", @@ -62,8 +65,8 @@ "tsx": "^4.15.6", "typescript": "^5.4.5", "typescript-eslint": "^8.0.0", - "unplugin-icons": "^0.19.3", - "unplugin-vue-components": "^0.27.4", + "unplugin-icons": "^0.22.0", + "unplugin-vue-components": "^0.28.0", "vite": "^5.4.19", "vite-plugin-dts": "^4.3.0", "vite-plugin-html": "^3.2.2", @@ -77,7 +80,6 @@ "@alloc/quick-lru": "^5.2.0", "@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@comfyorg/comfyui-electron-types": "^0.4.43", - "@comfyorg/litegraph": "^0.16.9", "@primevue/forms": "^4.2.5", "@primevue/themes": "^4.2.5", "@sentry/vue": "^8.48.0", @@ -96,6 +98,8 @@ "axios": "^1.8.2", "dompurify": "^3.2.5", "dotenv": "^16.4.5", + "extendable-media-recorder": "^9.2.27", + "extendable-media-recorder-wav-encoder": "^7.0.129", "firebase": "^11.6.0", "fuse.js": "^7.0.0", "jsondiffpatch": "^0.6.0", @@ -105,6 +109,7 @@ "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.2.5", + "semver": "^7.7.2", "three": "^0.170.0", "tiptap-markdown": "^0.8.10", "vue": "^3.5.13", diff --git a/playwright.config.ts b/playwright.config.ts index 0bccb1582..e259c74ac 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -49,6 +49,13 @@ export default defineConfig({ grep: /@2x/ // Run all tests tagged with @2x }, + { + name: 'chromium-0.5x', + use: { ...devices['Desktop Chrome'], deviceScaleFactor: 0.5 }, + timeout: 15000, + grep: /@0.5x/ // Run all tests tagged with @0.5x + }, + // { // name: 'firefox', // use: { ...devices['Desktop Firefox'] }, diff --git a/scripts/prepare-types.js b/scripts/prepare-types.js index 86890ed33..625579dad 100644 --- a/scripts/prepare-types.js +++ b/scripts/prepare-types.js @@ -16,9 +16,7 @@ const typesPackage = { homepage: mainPackage.homepage, description: `TypeScript definitions for ${mainPackage.name}`, license: mainPackage.license, - dependencies: { - '@comfyorg/litegraph': mainPackage.dependencies['@comfyorg/litegraph'] - }, + dependencies: {}, peerDependencies: { vue: mainPackage.dependencies.vue, zod: mainPackage.dependencies.zod diff --git a/src/CLAUDE.md b/src/CLAUDE.md new file mode 100644 index 000000000..a4b273c7b --- /dev/null +++ b/src/CLAUDE.md @@ -0,0 +1,57 @@ +# Source Code Guidelines + +## Service Layer + +### API Calls + +- Use `api.apiURL()` for backend endpoints +- Use `api.fileURL()` for static files + +#### โœ… Correct Usage +```typescript +// Backend API call +const response = await api.get(api.apiURL('/prompt')) + +// Static file +const template = await fetch(api.fileURL('/templates/default.json')) +``` + +#### โŒ Incorrect Usage +```typescript +// WRONG - Direct URL construction +const response = await fetch('/api/prompt') +const template = await fetch('/templates/default.json') +``` + +### Error Handling + +- User-friendly and actionable messages +- Proper error propagation + +### Security + +- Sanitize HTML with DOMPurify +- Validate trusted sources +- Never log secrets + +## State Management (Stores) + +### Store Design + +- Follow domain-driven design +- Clear public interfaces +- Restrict extension access + +### Best Practices + +- Use TypeScript for type safety +- Implement proper error handling +- Clean up subscriptions +- Avoid @ts-expect-error + +## General Guidelines + +- Use lodash for utility functions +- Implement proper TypeScript types +- Follow Vue 3 composition API style guide +- Use vue-i18n for ALL user-facing strings in `src/locales/en/main.json` diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 289392447..d5a6285d3 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -616,7 +616,8 @@ audio.comfy-audio.empty-audio-widget { .comfy-load-3d canvas, .comfy-load-3d-animation canvas, .comfy-preview-3d canvas, -.comfy-preview-3d-animation canvas{ +.comfy-preview-3d-animation canvas, +.comfy-load-3d-viewer canvas{ display: flex; width: 100% !important; height: 100% !important; diff --git a/src/assets/icons/README.md b/src/assets/icons/README.md new file mode 100644 index 000000000..cd92f1ddf --- /dev/null +++ b/src/assets/icons/README.md @@ -0,0 +1,310 @@ +# ComfyUI Icons Guide + +ComfyUI supports three types of icons that can be used throughout the interface. All icons are automatically imported - no manual imports needed! + +## Quick Start - Code Examples + +### 1. PrimeIcons + +```vue + +``` + +[Browse all PrimeIcons โ†’](https://primevue.org/icons/#list) + +### 2. Iconify Icons (Recommended) + +```vue + +``` + +[Browse 200,000+ icons โ†’](https://icon-sets.iconify.design/) + +### 3. Custom Icons + +```vue + +``` + +## Icon Usage Patterns + +### In Buttons + +```vue + +``` + +### Conditional Icons + +```vue + +``` + +### With Tooltips + +```vue + +``` + +## Using Iconify Icons + +### Finding Icons + +1. Visit [Iconify Icon Sets](https://icon-sets.iconify.design/) +2. Search or browse collections +3. Click on any icon to get its name +4. Use with `i-[collection]:[icon-name]` format + +### Popular Collections + +- **Lucide** (`i-lucide:`) - Our primary icon set, clean and consistent +- **Material Design Icons** (`i-mdi:`) - Comprehensive Material Design icons +- **Heroicons** (`i-heroicons:`) - Beautiful hand-crafted SVG icons +- **Tabler** (`i-tabler:`) - 3000+ free SVG icons +- **Carbon** (`i-carbon:`) - IBM's design system icons + +## Adding Custom Icons + +### 1. Add Your SVG + +Place your SVG file in `src/assets/icons/custom/`: + +``` +src/assets/icons/custom/ +โ”œโ”€โ”€ workflow-duplicate.svg +โ”œโ”€โ”€ node-preview.svg +โ””โ”€โ”€ your-icon.svg +``` + +### 2. SVG Format Requirements + +```xml + + + + +``` + +**Important:** +- Use `viewBox` for proper scaling (24x24 is standard) +- Don't include `width` or `height` attributes +- Use `currentColor` for theme-aware icons +- Keep SVGs optimized and simple + +### 3. Use Immediately + +```vue + +``` + +No imports needed - icons are auto-discovered! + +## Icon Guidelines + +### Naming Conventions + +- **Files**: `kebab-case.svg` (workflow-icon.svg) +- **Usage**: `` + +### Size & Styling + +```vue + +``` + +### Theme Compatibility + +Always use `currentColor` in SVGs for automatic theme adaptation: + +```xml + + + + + + + + + +``` + +## Migration Guide + +### From PrimeIcons to Iconify/Custom + +```vue + +``` + +### From Inline SVG to Custom Icon + +```vue + +``` + +## Technical Details + +### Auto-Import System + +Icons are automatically imported using `unplugin-icons` - no manual imports needed! Just use the icon component directly. + +### Configuration + +The icon system is configured in `vite.config.mts`: + +```typescript +Icons({ + compiler: 'vue3', + customCollections: { + comfy: FileSystemIconLoader('src/assets/icons/custom') + } +}) +``` + +### TypeScript Support + +Icons are fully typed. If TypeScript doesn't recognize a new custom icon: + +1. Restart the dev server +2. Ensure the SVG file is valid +3. Check filename follows kebab-case + +## Troubleshooting + +### Icon Not Showing +1. **Check filename**: Must be kebab-case without special characters +2. **Restart dev server**: Required after adding new icons +3. **Verify SVG**: Ensure it's valid SVG syntax +4. **Check console**: Look for Vue component resolution errors + +### Icon Wrong Color +- Replace hardcoded colors with `currentColor` +- Use `stroke="currentColor"` for outlines +- Use `fill="currentColor"` for filled shapes + +### Icon Wrong Size +- Remove `width` and `height` from SVG +- Ensure `viewBox` is present +- Use CSS classes for sizing: `class="w-6 h-6"` + +## Best Practices + +1. **Optimize SVGs**: Use tools like [SVGO](https://jakearchibald.github.io/svgomg/) to minimize file size +2. **Consistent viewBox**: Stick to 24x24 or 16x16 for consistency +3. **Semantic names**: Use descriptive names like `workflow-duplicate` not `icon1` +4. **Theme support**: Always use `currentColor` for adaptable icons +5. **Test both themes**: Verify icons look good in light and dark modes + +## Adding Icon Collections + +To add an entire icon set from npm: + +1. Install the icon package +2. Configure in `vite.config.mts` +3. Use with the appropriate prefix + +See the [unplugin-icons documentation](https://github.com/unplugin/unplugin-icons) for details. + +## Resources + +- [PrimeIcons List](https://primevue.org/icons/#list) +- [Iconify Icon Browser](https://icon-sets.iconify.design/) +- [Lucide Icons](https://lucide.dev/icons/) +- [unplugin-icons docs](https://github.com/unplugin/unplugin-icons) diff --git a/src/assets/icons/custom/workflow.svg b/src/assets/icons/custom/workflow.svg new file mode 100644 index 000000000..72f90c1a4 --- /dev/null +++ b/src/assets/icons/custom/workflow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/CLAUDE.md b/src/components/CLAUDE.md new file mode 100644 index 000000000..432fe7144 --- /dev/null +++ b/src/components/CLAUDE.md @@ -0,0 +1,45 @@ +# Component Guidelines + +## Vue 3 Composition API + +- Use setup() function +- Destructure props (Vue 3.5 style) +- Use ref/reactive for state +- Implement computed() for derived state +- Use provide/inject for dependency injection + +## Component Communication + +- Prefer `emit/@event-name` for state changes +- Use `defineExpose` only for imperative operations (`form.validate()`, `modal.open()`) +- Events promote loose coupling + +## UI Framework + +- Deprecated PrimeVue component replacements: + - Dropdown โ†’ Select + - OverlayPanel โ†’ Popover + - Calendar โ†’ DatePicker + - InputSwitch โ†’ ToggleSwitch + - Sidebar โ†’ Drawer + - Chips โ†’ AutoComplete with multiple enabled + - TabMenu โ†’ Tabs without panels + - Steps โ†’ Stepper without panels + - InlineMessage โ†’ Message + +## Styling + +- Use Tailwind CSS only (no custom CSS) +- Dark theme: use "dark-theme:" prefix +- For common operations, try to use existing VueUse composables that automatically handle effect scope + - Example: Use `useElementHover` instead of manually managing mouseover/mouseout event listeners + - Example: Use `useIntersectionObserver` for visibility detection instead of custom scroll handlers + +## Best Practices + +- Extract complex conditionals to computed +- Implement cleanup for async operations +- Use vue-i18n for ALL UI strings +- Use lifecycle hooks: onMounted, onUpdated +- Use Teleport/Suspense when needed +- Proper props and emits definitions diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index c9d75d009..be113867a 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -30,10 +30,11 @@ import ComfyQueueButton from './ComfyQueueButton.vue' const settingsStore = useSettingStore() -const visible = computed( - () => settingsStore.get('Comfy.UseNewMenu') !== 'Disabled' -) +const position = computed(() => settingsStore.get('Comfy.UseNewMenu')) +const visible = computed(() => position.value !== 'Disabled') + +const topMenuRef = inject>('topMenuRef') const panelRef = ref(null) const dragHandleRef = ref(null) const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', false) @@ -49,7 +50,16 @@ const { } = useDraggable(panelRef, { initialValue: { x: 0, y: 0 }, handle: dragHandleRef, - containerElement: document.body + containerElement: document.body, + onMove: (event) => { + // Prevent dragging the menu over the top of the tabs + if (position.value === 'Top') { + const minY = topMenuRef?.value?.getBoundingClientRect().top ?? 40 + if (event.y < minY) { + event.y = minY + } + } + } }) // Update storedPosition when x or y changes @@ -182,7 +192,6 @@ const adjustMenuPosition = () => { useEventListener(window, 'resize', adjustMenuPosition) -const topMenuRef = inject>('topMenuRef') const topMenuBounds = useElementBounding(topMenuRef) const overlapThreshold = 20 // pixels const isOverlappingWithTopMenu = computed(() => { diff --git a/src/components/bottomPanel/BottomPanel.vue b/src/components/bottomPanel/BottomPanel.vue index a49268753..63d6874c0 100644 --- a/src/components/bottomPanel/BottomPanel.vue +++ b/src/components/bottomPanel/BottomPanel.vue @@ -11,18 +11,33 @@ class="p-3 border-none" > - {{ tab.title.toUpperCase() }} + {{ + shouldCapitalizeTab(tab.id) + ? tab.title.toUpperCase() + : tab.title + }} - diff --git a/src/components/sidebar/SidebarSettingsToggleIcon.vue b/src/components/sidebar/SidebarSettingsToggleIcon.vue deleted file mode 100644 index 3b214fb43..000000000 --- a/src/components/sidebar/SidebarSettingsToggleIcon.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/components/sidebar/SidebarShortcutsToggleButton.vue b/src/components/sidebar/SidebarShortcutsToggleButton.vue new file mode 100644 index 000000000..f2e4ec400 --- /dev/null +++ b/src/components/sidebar/SidebarShortcutsToggleButton.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/components/sidebar/SidebarThemeToggleIcon.vue b/src/components/sidebar/SidebarThemeToggleIcon.vue deleted file mode 100644 index 8d64ff4ef..000000000 --- a/src/components/sidebar/SidebarThemeToggleIcon.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/src/components/templates/TemplateSearchBar.vue b/src/components/templates/TemplateSearchBar.vue new file mode 100644 index 000000000..17b564b12 --- /dev/null +++ b/src/components/templates/TemplateSearchBar.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/components/templates/TemplateWorkflowCardSkeleton.vue b/src/components/templates/TemplateWorkflowCardSkeleton.vue new file mode 100644 index 000000000..00bf73839 --- /dev/null +++ b/src/components/templates/TemplateWorkflowCardSkeleton.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/components/templates/TemplateWorkflowView.spec.ts b/src/components/templates/TemplateWorkflowView.spec.ts index a70e828a5..6860797c6 100644 --- a/src/components/templates/TemplateWorkflowView.spec.ts +++ b/src/components/templates/TemplateWorkflowView.spec.ts @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' +import { createI18n } from 'vue-i18n' import TemplateWorkflowView from '@/components/templates/TemplateWorkflowView.vue' import { TemplateInfo } from '@/types/workflowTemplateTypes' @@ -53,10 +54,46 @@ vi.mock('@/components/templates/TemplateWorkflowList.vue', () => ({ } })) +vi.mock('@/components/templates/TemplateSearchBar.vue', () => ({ + default: { + template: '', + props: ['searchQuery', 'filteredCount'], + emits: ['update:searchQuery', 'clearFilters'] + } +})) + +vi.mock('@/components/templates/TemplateWorkflowCardSkeleton.vue', () => ({ + default: { + template: '
' + } +})) + vi.mock('@vueuse/core', () => ({ useLocalStorage: () => 'grid' })) +vi.mock('@/composables/useIntersectionObserver', () => ({ + useIntersectionObserver: vi.fn() +})) + +vi.mock('@/composables/useLazyPagination', () => ({ + useLazyPagination: (items: any) => ({ + paginatedItems: items, + isLoading: { value: false }, + hasMoreItems: { value: false }, + loadNextPage: vi.fn(), + reset: vi.fn() + }) +})) + +vi.mock('@/composables/useTemplateFiltering', () => ({ + useTemplateFiltering: (templates: any) => ({ + searchQuery: { value: '' }, + filteredTemplates: templates, + filteredCount: { value: templates.value?.length || 0 } + }) +})) + describe('TemplateWorkflowView', () => { const createTemplate = (name: string): TemplateInfo => ({ name, @@ -67,6 +104,18 @@ describe('TemplateWorkflowView', () => { }) const mountView = (props = {}) => { + const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: { + templateWorkflows: { + loadingMore: 'Loading more...' + } + } + } + }) + return mount(TemplateWorkflowView, { props: { title: 'Test Templates', @@ -79,6 +128,9 @@ describe('TemplateWorkflowView', () => { ], loading: null, ...props + }, + global: { + plugins: [i18n] } }) } diff --git a/src/components/templates/TemplateWorkflowView.vue b/src/components/templates/TemplateWorkflowView.vue index 174a91201..8a866cdd1 100644 --- a/src/components/templates/TemplateWorkflowView.vue +++ b/src/components/templates/TemplateWorkflowView.vue @@ -1,24 +1,31 @@ @@ -54,12 +78,21 @@ import { useLocalStorage } from '@vueuse/core' import DataView from 'primevue/dataview' import SelectButton from 'primevue/selectbutton' +import { computed, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import TemplateSearchBar from '@/components/templates/TemplateSearchBar.vue' import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue' +import TemplateWorkflowCardSkeleton from '@/components/templates/TemplateWorkflowCardSkeleton.vue' import TemplateWorkflowList from '@/components/templates/TemplateWorkflowList.vue' +import { useIntersectionObserver } from '@/composables/useIntersectionObserver' +import { useLazyPagination } from '@/composables/useLazyPagination' +import { useTemplateFiltering } from '@/composables/useTemplateFiltering' import type { TemplateInfo } from '@/types/workflowTemplateTypes' -defineProps<{ +const { t } = useI18n() + +const { title, sourceModule, categoryTitle, loading, templates } = defineProps<{ title: string sourceModule: string categoryTitle: string @@ -72,6 +105,59 @@ const layout = useLocalStorage<'grid' | 'list'>( 'grid' ) +const skeletonCount = 6 +const loadTrigger = ref(null) + +const templatesRef = computed(() => templates || []) + +const { searchQuery, filteredTemplates, filteredCount } = + useTemplateFiltering(templatesRef) + +// When searching, show all results immediately without pagination +// When not searching, use lazy pagination +const shouldUsePagination = computed(() => !searchQuery.value.trim()) + +// Lazy pagination setup using filtered templates +const { + paginatedItems: paginatedTemplates, + isLoading: isLoadingMore, + hasMoreItems: hasMoreTemplates, + loadNextPage, + reset +} = useLazyPagination(filteredTemplates, { + itemsPerPage: 12 +}) + +// Final templates to display +const displayTemplates = computed(() => { + return shouldUsePagination.value + ? paginatedTemplates.value + : filteredTemplates.value +}) +// Intersection observer for auto-loading (only when not searching) +useIntersectionObserver( + loadTrigger, + (entries) => { + const entry = entries[0] + if ( + entry?.isIntersecting && + shouldUsePagination.value && + hasMoreTemplates.value && + !isLoadingMore.value + ) { + void loadNextPage() + } + }, + { + rootMargin: '200px', + threshold: 0.1 + } +) + +watch([() => templates, searchQuery], () => { + reset() +}) + const emit = defineEmits<{ loadWorkflow: [name: string] }>() diff --git a/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts b/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts index 7d0fcc9c9..681d81238 100644 --- a/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts +++ b/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts @@ -12,6 +12,15 @@ vi.mock('@/components/templates/thumbnails/BaseThumbnail.vue', () => ({ } })) +vi.mock('@/components/common/LazyImage.vue', () => ({ + default: { + name: 'LazyImage', + template: + '', + props: ['src', 'alt', 'imageClass', 'imageStyle'] + } +})) + vi.mock('@vueuse/core', () => ({ useMouseInElement: () => ({ elementX: ref(50), @@ -35,23 +44,24 @@ describe('CompareSliderThumbnail', () => { it('renders both base and overlay images', () => { const wrapper = mountThumbnail() - const images = wrapper.findAll('img') - expect(images.length).toBe(2) - expect(images[0].attributes('src')).toBe('/base-image.jpg') - expect(images[1].attributes('src')).toBe('/overlay-image.jpg') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages.length).toBe(2) + expect(lazyImages[0].props('src')).toBe('/base-image.jpg') + expect(lazyImages[1].props('src')).toBe('/overlay-image.jpg') }) it('applies correct alt text to both images', () => { const wrapper = mountThumbnail({ alt: 'Custom Alt Text' }) - const images = wrapper.findAll('img') - expect(images[0].attributes('alt')).toBe('Custom Alt Text') - expect(images[1].attributes('alt')).toBe('Custom Alt Text') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages[0].props('alt')).toBe('Custom Alt Text') + expect(lazyImages[1].props('alt')).toBe('Custom Alt Text') }) it('applies clip-path style to overlay image', () => { const wrapper = mountThumbnail() - const overlay = wrapper.findAll('img')[1] - expect(overlay.attributes('style')).toContain('clip-path') + const overlayLazyImage = wrapper.findAllComponents({ name: 'LazyImage' })[1] + const imageStyle = overlayLazyImage.props('imageStyle') + expect(imageStyle.clipPath).toContain('inset') }) it('renders slider divider', () => { diff --git a/src/components/templates/thumbnails/CompareSliderThumbnail.vue b/src/components/templates/thumbnails/CompareSliderThumbnail.vue index 3a6d0e3a2..3633c5dc5 100644 --- a/src/components/templates/thumbnails/CompareSliderThumbnail.vue +++ b/src/components/templates/thumbnails/CompareSliderThumbnail.vue @@ -1,24 +1,24 @@