diff --git a/.claude/commands/create-hotfix-release.md b/.claude/commands/create-hotfix-release.md index de314309d..f35a8ad23 100644 --- a/.claude/commands/create-hotfix-release.md +++ b/.claude/commands/create-hotfix-release.md @@ -1,30 +1,85 @@ # Create Hotfix Release -This command guides you through creating a patch/hotfix release for ComfyUI Frontend with comprehensive safety checks and human confirmations at each step. +This command creates patch/hotfix releases for ComfyUI Frontend by backporting fixes to stable core branches. It handles both automated backports (preferred) and manual cherry-picking (fallback). + +**Process Overview:** +1. **Check automated backports first** (via labels) +2. **Skip to version bump** if backports already merged +3. **Manual cherry-picking** if automation failed +4. **Create patch release** with version bump +5. **Publish GitHub release** (manually uncheck "latest") +6. **Update ComfyUI requirements.txt** via PR -Create a hotfix release by cherry-picking commits or PR commits from main to a core branch: $ARGUMENTS +Create a hotfix release by backporting commits/PRs from main to a core branch: $ARGUMENTS Expected format: Comma-separated list of commits or PR numbers Examples: -- `abc123,def456,ghi789` (commits) -- `#1234,#5678` (PRs) -- `abc123,#1234,def456` (mixed) +- `#1234,#5678` (PRs - preferred) +- `abc123,def456` (commit hashes) +- `#1234,abc123` (mixed) -If no arguments provided, the command will help identify the correct core branch and guide you through selecting commits/PRs. +If no arguments provided, the command will guide you through identifying commits/PRs to backport. ## Prerequisites -Before starting, ensure: -- You have push access to the repository -- GitHub CLI (`gh`) is authenticated -- You're on a clean working tree -- You understand the commits/PRs you're cherry-picking +- Push access to repository +- GitHub CLI (`gh`) authenticated +- Clean working tree +- Understanding of what fixes need backporting ## Hotfix Release Process -### Step 1: Identify Target Core Branch +### Step 1: Try Automated Backports First + +**Check if automated backports were attempted:** + +1. **For each PR, check existing backport labels:** + ```bash + gh pr view #1234 --json labels | jq -r '.labels[].name' + ``` + +2. **If no backport labels exist, add them now:** + ```bash + # Add backport labels (this triggers automated backports) + gh pr edit #1234 --add-label "needs-backport" + gh pr edit #1234 --add-label "1.24" # Replace with target version + ``` + +3. **Check for existing backport PRs:** + ```bash + # Check for backport PRs created by automation + PR_NUMBER=${ARGUMENTS%%,*} # Extract first PR number from arguments + PR_NUMBER=${PR_NUMBER#\#} # Remove # prefix + gh pr list --search "backport-${PR_NUMBER}-to" --json number,title,state,baseRefName + ``` + +4. **Handle existing backport scenarios:** + + **Scenario A: Automated backports already merged** + ```bash + # Check if backport PRs were merged to core branches + gh pr list --search "backport-${PR_NUMBER}-to" --state merged + ``` + - If backport PRs are merged → Skip to Step 10 (Version Bump) + - **CONFIRMATION**: Automated backports completed, proceeding to version bump? + + **Scenario B: Automated backport PRs exist but not merged** + ```bash + # Show open backport PRs that need merging + gh pr list --search "backport-${PR_NUMBER}-to" --state open + ``` + - **ACTION REQUIRED**: Merge the existing backport PRs first + - Use: `gh pr merge [PR_NUMBER] --merge` for each backport PR + - After merging, return to this command and skip to Step 10 (Version Bump) + - **CONFIRMATION**: Have you merged all backport PRs? Ready to proceed to version bump? + + **Scenario C: No automated backports or they failed** + - Continue to Step 2 for manual cherry-picking + - **CONFIRMATION**: Proceeding with manual cherry-picking because automation failed? + +### Step 2: Identify Target Core Branch 1. Fetch the current ComfyUI requirements.txt from master branch: ```bash @@ -36,7 +91,7 @@ Before starting, ensure: 5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*` 6. **CONFIRMATION REQUIRED**: Is `core/X.Y` the correct target branch? -### Step 2: Parse and Validate Arguments +### Step 3: Parse and Validate Arguments 1. Parse the comma-separated list of commits/PRs 2. For each item: @@ -49,7 +104,7 @@ Before starting, ensure: - **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits? 4. Validate all commit hashes exist in the repository -### Step 3: Analyze Target Changes +### Step 4: Analyze Target Changes 1. For each commit/PR to cherry-pick: - Display commit hash, author, date @@ -60,7 +115,7 @@ Before starting, ensure: 2. Identify potential conflicts by checking changed files 3. **CONFIRMATION REQUIRED**: Proceed with these commits? -### Step 4: Create Hotfix Branch +### Step 5: Create Hotfix Branch 1. Checkout the core branch (e.g., `core/1.23`) 2. Pull latest changes: `git pull origin core/X.Y` @@ -69,7 +124,7 @@ Before starting, ensure: - Example: `hotfix/1.23.4-20241120` 5. **CONFIRMATION REQUIRED**: Created branch correctly? -### Step 5: Cherry-pick Changes +### Step 6: Cherry-pick Changes For each commit: 1. Attempt cherry-pick: `git cherry-pick ` @@ -83,7 +138,7 @@ For each commit: - Run validation: `pnpm typecheck && pnpm lint` 4. **CONFIRMATION REQUIRED**: Cherry-pick successful and valid? -### Step 6: Create PR to Core Branch +### Step 7: Create PR to Core Branch 1. Push the hotfix branch: `git push origin hotfix/-` 2. Create PR using gh CLI: @@ -100,7 +155,7 @@ For each commit: - Impact assessment 5. **CONFIRMATION REQUIRED**: PR created correctly? -### Step 7: Wait for Tests +### Step 8: Wait for Tests 1. Monitor PR checks: `gh pr checks` 2. Display test results as they complete @@ -111,7 +166,7 @@ For each commit: 4. Wait for all required checks to pass 5. **CONFIRMATION REQUIRED**: All tests passing? -### Step 8: Merge Hotfix PR +### Step 9: Merge Hotfix PR 1. Verify all checks have passed 2. Check for required approvals @@ -119,7 +174,7 @@ For each commit: 4. Delete the hotfix branch 5. **CONFIRMATION REQUIRED**: PR merged successfully? -### Step 9: Create Version Bump +### Step 10: Create Version Bump 1. Checkout the core branch: `git checkout core/X.Y` 2. Pull latest changes: `git pull origin core/X.Y` @@ -131,7 +186,7 @@ For each commit: 7. Commit: `git commit -m "[release] Bump version to 1.23.5"` 8. **CONFIRMATION REQUIRED**: Version bump correct? -### Step 10: Create Release PR +### Step 11: Create Release PR 1. Push release branch: `git push origin release/1.23.5` 2. Create PR with Release label: @@ -184,7 +239,7 @@ For each commit: ``` 5. **CONFIRMATION REQUIRED**: Release PR has "Release" label? -### Step 11: Monitor Release Process +### Step 12: Monitor Release Process 1. Wait for PR checks to pass 2. **FINAL CONFIRMATION**: Ready to trigger release by merging? @@ -199,7 +254,102 @@ For each commit: - PyPI upload - pnpm types publication -### Step 12: Post-Release Verification +### Step 13: Manually Publish Draft Release + +**CRITICAL**: The release workflow creates a DRAFT release. You must manually publish it: + +1. **Go to GitHub Releases:** https://github.com/Comfy-Org/ComfyUI_frontend/releases +2. **Find the DRAFT release** (e.g., "v1.23.5 Draft") +3. **Click "Edit release"** +4. **UNCHECK "Set as the latest release"** ⚠️ **CRITICAL** + - This prevents the hotfix from showing as "latest" + - Main branch should always be "latest release" +5. **Click "Publish release"** +6. **CONFIRMATION REQUIRED**: Draft release published with "latest" unchecked? + +### Step 14: Create ComfyUI Requirements.txt Update PR + +**IMPORTANT**: Create PR to update ComfyUI's requirements.txt via fork: + +1. **Setup fork (if needed):** + ```bash + # Check if fork already exists + if gh repo view ComfyUI --json owner | jq -r '.owner.login' | grep -q "$(gh api user --jq .login)"; then + echo "Fork already exists" + else + # Fork the ComfyUI repository + gh repo fork comfyanonymous/ComfyUI --clone=false + echo "Created fork of ComfyUI" + fi + ``` + +2. **Clone fork and create branch:** + ```bash + # Clone your fork (or use existing clone) + GITHUB_USER=$(gh api user --jq .login) + if [ ! -d "ComfyUI-fork" ]; then + gh repo clone ${GITHUB_USER}/ComfyUI ComfyUI-fork + fi + + cd ComfyUI-fork + git checkout master + git pull origin master + + # Create update branch + BRANCH_NAME="update-frontend-${NEW_VERSION}" + git checkout -b ${BRANCH_NAME} + ``` + +3. **Update requirements.txt:** + ```bash + # Update the version in requirements.txt + sed -i "s/comfyui-frontend-package==[0-9].*$/comfyui-frontend-package==${NEW_VERSION}/" requirements.txt + + # Verify the change + grep "comfyui-frontend-package" requirements.txt + + # Commit the change + git add requirements.txt + git commit -m "Bump frontend to ${NEW_VERSION}" + git push origin ${BRANCH_NAME} + ``` + +4. **Create PR from fork:** + ```bash + # Create PR using gh CLI from fork + gh pr create \ + --repo comfyanonymous/ComfyUI \ + --title "Bump frontend to ${NEW_VERSION}" \ + --body "$(cat <> $GITHUB_OUTPUT + exit 0 + fi + + echo "Found existing backport PRs:" + echo "$EXISTING_BACKPORTS" + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates" + - name: Extract version labels + if: steps.check-existing.outputs.skip != 'true' id: versions run: | # Extract version labels (e.g., "1.24", "1.22") @@ -52,6 +72,7 @@ jobs: echo "Found version labels: ${VERSIONS}" - name: Backport commits + if: steps.check-existing.outputs.skip != 'true' id: backport env: PR_NUMBER: ${{ github.event.pull_request.number }} @@ -109,7 +130,7 @@ jobs: fi - name: Create PR for each successful backport - if: steps.backport.outputs.success + if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success env: GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} run: | @@ -141,7 +162,7 @@ jobs: done - name: Comment on failures - if: failure() && steps.backport.outputs.failed + if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed env: GH_TOKEN: ${{ github.token }} run: | diff --git a/.github/workflows/create-release-candidate-branch.yaml b/.github/workflows/create-release-candidate-branch.yaml index 84b545478..e3fcd9e2b 100644 --- a/.github/workflows/create-release-candidate-branch.yaml +++ b/.github/workflows/create-release-candidate-branch.yaml @@ -128,45 +128,6 @@ jobs: 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' diff --git a/.husky/pre-commit b/.husky/pre-commit index 6b8a399e4..578271509 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env bash -npx lint-staged -npx tsx scripts/check-unused-i18n-keys.ts \ No newline at end of file +pnpm exec lint-staged +pnpm exec tsx scripts/check-unused-i18n-keys.ts diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000..ec1fc17d0 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# Run Knip with cache via package script +pnpm knip + diff --git a/browser_tests/fixtures/ComfyMouse.ts b/browser_tests/fixtures/ComfyMouse.ts index 306f4352b..dfb0281eb 100644 --- a/browser_tests/fixtures/ComfyMouse.ts +++ b/browser_tests/fixtures/ComfyMouse.ts @@ -10,7 +10,7 @@ import type { Position } from './types' * - {@link Mouse.move} * - {@link Mouse.up} */ -export interface DragOptions { +interface DragOptions { button?: 'left' | 'right' | 'middle' clickCount?: number steps?: number diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index f64ca5c94..c32dd3937 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -453,6 +453,32 @@ export class ComfyPage { await workflowsTab.close() } + /** + * Attach a screenshot to the test report. + * By default, screenshots are only taken in non-CI environments. + * @param name - Name for the screenshot attachment + * @param options - Optional configuration + * @param options.runInCI - Whether to take screenshot in CI (default: false) + * @param options.fullPage - Whether to capture full page (default: false) + */ + async attachScreenshot( + name: string, + options: { runInCI?: boolean; fullPage?: boolean } = {} + ) { + const { runInCI = false, fullPage = false } = options + + // Skip in CI unless explicitly requested + if (process.env.CI && !runInCI) { + return + } + + const testInfo = comfyPageFixture.info() + await testInfo.attach(name, { + body: await this.page.screenshot({ fullPage }), + contentType: 'image/png' + }) + } + async resetView() { if (await this.resetViewButton.isVisible()) { await this.resetViewButton.click() diff --git a/browser_tests/fixtures/components/Topbar.ts b/browser_tests/fixtures/components/Topbar.ts index f2c9dfa16..04a9117ce 100644 --- a/browser_tests/fixtures/components/Topbar.ts +++ b/browser_tests/fixtures/components/Topbar.ts @@ -1,7 +1,13 @@ -import { Locator, Page } from '@playwright/test' +import { Locator, Page, expect } from '@playwright/test' export class Topbar { - constructor(public readonly page: Page) {} + private readonly menuLocator: Locator + private readonly menuTrigger: Locator + + constructor(public readonly page: Page) { + this.menuLocator = page.locator('.comfy-command-menu') + this.menuTrigger = page.locator('.comfyui-logo-wrapper') + } async getTabNames(): Promise { return await this.page @@ -15,10 +21,33 @@ export class Topbar { .innerText() } - getMenuItem(itemLabel: string): Locator { + /** + * Get a menu item by its label, optionally within a specific parent container + */ + getMenuItem(itemLabel: string, parent?: Locator): Locator { + if (parent) { + return parent.locator(`.p-tieredmenu-item:has-text("${itemLabel}")`) + } + return this.page.locator(`.p-menubar-item-label:text-is("${itemLabel}")`) } + /** + * Get the visible submenu (last visible submenu in case of nested menus) + */ + getVisibleSubmenu(): Locator { + return this.page.locator('.p-tieredmenu-submenu:visible').last() + } + + /** + * Check if a menu item has an active checkmark + */ + async isMenuItemActive(menuItem: Locator): Promise { + const checkmark = menuItem.locator('.pi-check') + const classes = await checkmark.getAttribute('class') + return classes ? !classes.includes('invisible') : false + } + getWorkflowTab(tabName: string): Locator { return this.page .locator(`.workflow-tabs .workflow-label:has-text("${tabName}")`) @@ -66,10 +95,50 @@ export class Topbar { async openTopbarMenu() { await this.page.waitForTimeout(1000) - await this.page.locator('.comfyui-logo-wrapper').click() - const menu = this.page.locator('.comfy-command-menu') - await menu.waitFor({ state: 'visible' }) - return menu + await this.menuTrigger.click() + await this.menuLocator.waitFor({ state: 'visible' }) + return this.menuLocator + } + + /** + * Close the topbar menu by clicking outside + */ + async closeTopbarMenu() { + await this.page.locator('body').click({ position: { x: 10, y: 10 } }) + await expect(this.menuLocator).not.toBeVisible() + } + + /** + * Navigate to a submenu by hovering over a menu item + */ + async openSubmenu(menuItemLabel: string): Promise { + const menuItem = this.getMenuItem(menuItemLabel) + await menuItem.hover() + const submenu = this.getVisibleSubmenu() + await submenu.waitFor({ state: 'visible' }) + return submenu + } + + /** + * Get theme menu items and interact with theme switching + */ + async getThemeMenuItems() { + const themeSubmenu = await this.openSubmenu('Theme') + return { + submenu: themeSubmenu, + darkTheme: this.getMenuItem('Dark (Default)', themeSubmenu), + lightTheme: this.getMenuItem('Light', themeSubmenu) + } + } + + /** + * Switch to a specific theme + */ + async switchTheme(theme: 'dark' | 'light') { + const { darkTheme, lightTheme } = await this.getThemeMenuItems() + const themeItem = theme === 'dark' ? darkTheme : lightTheme + const themeLabel = themeItem.locator('.p-menubar-item-label') + await themeLabel.click() } async triggerTopbarCommand(path: string[]) { @@ -79,9 +148,7 @@ export class Topbar { const menu = await this.openTopbarMenu() const tabName = path[0] - const topLevelMenuItem = this.page.locator( - `.p-menubar-item-label:text-is("${tabName}")` - ) + const topLevelMenuItem = this.getMenuItem(tabName) const topLevelMenu = menu .locator('.p-tieredmenu-item') .filter({ has: topLevelMenuItem }) diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 8a52d8b66..c9bf88a91 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -134,7 +134,7 @@ export class SubgraphSlotReference { } } -export class NodeSlotReference { +class NodeSlotReference { constructor( readonly type: 'input' | 'output', readonly index: number, @@ -201,7 +201,7 @@ export class NodeSlotReference { } } -export class NodeWidgetReference { +class NodeWidgetReference { constructor( readonly index: number, readonly node: NodeReference diff --git a/browser_tests/tests/menu.spec.ts b/browser_tests/tests/menu.spec.ts index 355acb590..fa46778a4 100644 --- a/browser_tests/tests/menu.spec.ts +++ b/browser_tests/tests/menu.spec.ts @@ -178,6 +178,72 @@ test.describe('Menu', () => { await comfyPage.menu.topbar.triggerTopbarCommand(['ext', 'foo-command']) expect(await comfyPage.getVisibleToastCount()).toBe(1) }) + + test('Can navigate Theme menu and switch between Dark and Light themes', async ({ + comfyPage + }) => { + const { topbar } = comfyPage.menu + + // Take initial screenshot with default theme + await comfyPage.attachScreenshot('theme-initial') + + // Open the topbar menu + const menu = await topbar.openTopbarMenu() + await expect(menu).toBeVisible() + + // Get theme menu items + const { + submenu: themeSubmenu, + darkTheme: darkThemeItem, + lightTheme: lightThemeItem + } = await topbar.getThemeMenuItems() + + await expect(darkThemeItem).toBeVisible() + await expect(lightThemeItem).toBeVisible() + + // Switch to Light theme + await topbar.switchTheme('light') + + // Verify menu stays open and Light theme shows as active + await expect(menu).toBeVisible() + await expect(themeSubmenu).toBeVisible() + + // Check that Light theme is active + expect(await topbar.isMenuItemActive(lightThemeItem)).toBe(true) + + // Screenshot with light theme active + await comfyPage.attachScreenshot('theme-menu-light-active') + + // Verify ColorPalette setting is set to "light" + expect(await comfyPage.getSetting('Comfy.ColorPalette')).toBe('light') + + // Close menu to see theme change + await topbar.closeTopbarMenu() + + // Re-open menu and get theme items again + await topbar.openTopbarMenu() + const themeItems2 = await topbar.getThemeMenuItems() + + // Switch back to Dark theme + await topbar.switchTheme('dark') + + // Verify menu stays open and Dark theme shows as active + await expect(menu).toBeVisible() + await expect(themeItems2.submenu).toBeVisible() + + // Check that Dark theme is active and Light theme is not + expect(await topbar.isMenuItemActive(themeItems2.darkTheme)).toBe(true) + expect(await topbar.isMenuItemActive(themeItems2.lightTheme)).toBe(false) + + // Screenshot with dark theme active + await comfyPage.attachScreenshot('theme-menu-dark-active') + + // Verify ColorPalette setting is set to "dark" + expect(await comfyPage.getSetting('Comfy.ColorPalette')).toBe('dark') + + // Close menu + await topbar.closeTopbarMenu() + }) }) // Only test 'Top' to reduce test time. diff --git a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png index e638f12ce..7aa22906b 100644 Binary files a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png and b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png differ diff --git a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png index e638f12ce..41bb283d9 100644 Binary files a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png and b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png differ diff --git a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-selection-no-border-chromium-linux.png b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-selection-no-border-chromium-linux.png index 3cec1c675..2006231c7 100644 Binary files a/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-selection-no-border-chromium-linux.png and b/browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-selection-no-border-chromium-linux.png differ diff --git a/docs/adr/0004-crdt-based-layout-system.md b/docs/adr/0003-crdt-based-layout-system.md similarity index 99% rename from docs/adr/0004-crdt-based-layout-system.md rename to docs/adr/0003-crdt-based-layout-system.md index ceb483a6f..1eceb2a1f 100644 --- a/docs/adr/0004-crdt-based-layout-system.md +++ b/docs/adr/0003-crdt-based-layout-system.md @@ -1,4 +1,4 @@ -# 4. Centralized Layout Management with CRDT +# 3. Centralized Layout Management with CRDT Date: 2025-08-27 diff --git a/docs/adr/README.md b/docs/adr/README.md index 1b267345e..00e50a639 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -12,6 +12,7 @@ An Architecture Decision Record captures an important architectural decision mad |-----|-------|--------|------| | [0001](0001-merge-litegraph-into-frontend.md) | Merge LiteGraph.js into ComfyUI Frontend | Accepted | 2025-08-05 | | [0002](0002-monorepo-conversion.md) | Restructure as a Monorepo | Accepted | 2025-08-25 | +| [0003](0003-crdt-based-layout-system.md) | Centralized Layout Management with CRDT | Proposed | 2025-08-27 | ## Creating a New ADR diff --git a/eslint.config.js b/eslint.config.js index 59c5f46df..7e3248b20 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -62,6 +62,41 @@ export default [ '@typescript-eslint/prefer-as-const': 'off', 'unused-imports/no-unused-imports': 'error', 'vue/no-v-html': 'off', + // Enforce dark-theme: instead of dark: prefix + 'vue/no-restricted-class': ['error', '/^dark:/'], + // Restrict deprecated PrimeVue components + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'primevue/calendar', + message: + 'Calendar is deprecated in PrimeVue 4+. Use DatePicker instead: import DatePicker from "primevue/datepicker"' + }, + { + name: 'primevue/dropdown', + message: + 'Dropdown is deprecated in PrimeVue 4+. Use Select instead: import Select from "primevue/select"' + }, + { + name: 'primevue/inputswitch', + message: + 'InputSwitch is deprecated in PrimeVue 4+. Use ToggleSwitch instead: import ToggleSwitch from "primevue/toggleswitch"' + }, + { + name: 'primevue/overlaypanel', + message: + 'OverlayPanel is deprecated in PrimeVue 4+. Use Popover instead: import Popover from "primevue/popover"' + }, + { + name: 'primevue/sidebar', + message: + 'Sidebar is deprecated in PrimeVue 4+. Use Drawer instead: import Drawer from "primevue/drawer"' + } + ] + } + ], // i18n rules '@intlify/vue-i18n/no-raw-text': [ 'error', diff --git a/knip.config.ts b/knip.config.ts index 2c1167ab3..9df077d77 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -2,84 +2,56 @@ import type { KnipConfig } from 'knip' const config: KnipConfig = { entry: [ - 'build/**/*.ts', - 'scripts/**/*.{js,ts}', + '{build,scripts}/**/*.{js,ts}', + 'src/assets/css/style.css', 'src/main.ts', - 'vite.electron.config.mts', - 'vite.types.config.mts' - ], - project: [ - 'browser_tests/**/*.{js,ts}', - 'build/**/*.{js,ts,vue}', - 'scripts/**/*.{js,ts}', - 'src/**/*.{js,ts,vue}', - 'tests-ui/**/*.{js,ts,vue}', - '*.{js,ts,mts}' + 'src/scripts/ui/menu/index.ts', + 'src/types/index.ts' ], + project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}'], ignoreBinaries: ['only-allow', 'openapi-typescript'], ignoreDependencies: [ + // Weird importmap things + '@iconify/json', '@primeuix/forms', '@primeuix/styled', '@primeuix/utils', '@primevue/icons', - '@iconify/json', - 'tailwindcss', - 'tailwindcss-primeui', // Need to figure out why tailwind plugin isn't applying // Dev '@trivago/prettier-plugin-sort-imports' ], 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', - // Vitest litegraph config - 'vitest.litegraph.config.ts', - // 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', - // Design system components (may not be used immediately) - 'src/components/button/IconGroup.vue', - 'src/components/button/MoreButton.vue', - 'src/components/button/TextButton.vue', - 'src/components/card/CardTitle.vue', - 'src/components/card/CardDescription.vue', - 'src/components/input/SingleSelect.vue', + 'src/types/comfyRegistryTypes.ts', // Used by a custom node (that should move off of this) - 'src/scripts/ui/components/splitButton.ts', - // Generated file: openapi - 'src/types/comfyRegistryTypes.ts' + 'src/scripts/ui/components/splitButton.ts' ], - ignoreExportsUsedInFile: true, - // Vue-specific configuration - vue: true, - tailwind: 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: { - classMembers: 'off' + compilers: { + // https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199 + css: (text: string) => + [ + ...text.replaceAll('plugin', 'import').matchAll(/(?<=@)import[^;]+/g) + ].join('\n') + }, + vite: { + config: ['vite?(.*).config.mts'] + }, + vitest: { + config: ['vitest?(.*).config.ts'], + entry: [ + '**/*.{bench,test,test-d,spec}.?(c|m)[jt]s?(x)', + '**/__mocks__/**/*.[jt]s?(x)' + ] + }, + playwright: { + config: ['playwright?(.*).config.ts'], + entry: ['**/*.@(spec|test).?(c|m)[jt]s?(x)', 'browser_tests/**/*.ts'] }, tags: [ '-knipIgnoreUnusedButUsedByCustomNodes', '-knipIgnoreUnusedButUsedByVueNodesBranch' - ], - // Include dependencies analysis - includeEntryExports: true + ] } export default config diff --git a/package.json b/package.json index 0a8700fd2..8e3876eb9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.27.1", + "version": "1.27.2", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", @@ -25,8 +25,8 @@ "preinstall": "npx only-allow pnpm", "prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true", "preview": "nx preview", - "lint": "eslint src --cache", - "lint:fix": "eslint src --cache --fix", + "lint": "eslint src --cache --concurrency=auto", + "lint:fix": "eslint src --cache --fix --concurrency=auto", "lint:no-cache": "eslint src", "lint:fix:no-cache": "eslint src --fix", "knip": "knip --cache", @@ -62,7 +62,7 @@ "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.0.0", "@vue/test-utils": "^2.4.6", - "eslint": "^9.12.0", + "eslint": "^9.34.0", "eslint-config-prettier": "^10.1.2", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-storybook": "^9.1.1", @@ -84,7 +84,7 @@ "tailwindcss-primeui": "^0.6.1", "tsx": "^4.15.6", "typescript": "^5.4.5", - "typescript-eslint": "^8.0.0", + "typescript-eslint": "^8.42.0", "unplugin-icons": "^0.22.0", "unplugin-vue-components": "^0.28.0", "uuid": "^11.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b54c4837d..bbbfd7c1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,19 +176,19 @@ importers: version: 1.2.0 '@intlify/eslint-plugin-vue-i18n': specifier: ^3.2.0 - version: 3.2.0(eslint@9.12.0(jiti@2.4.2)) + version: 3.2.0(eslint@9.35.0(jiti@2.4.2)) '@lobehub/i18n-cli': specifier: ^1.25.1 version: 1.25.1(@types/react@19.1.9)(typescript@5.9.2)(use-sync-external-store@1.5.0(react@19.1.1))(ws@8.18.3)(zod@3.24.1) '@nx/eslint': specifier: 21.4.1 - version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1) + version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1) '@nx/playwright': specifier: 21.4.1 - version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) + version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/storybook': specifier: 21.4.1 - version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) '@nx/vite': specifier: 21.4.1 version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4) @@ -241,23 +241,23 @@ importers: specifier: ^2.4.6 version: 2.4.6 eslint: - specifier: ^9.12.0 - version: 9.12.0(jiti@2.4.2) + specifier: ^9.34.0 + version: 9.35.0(jiti@2.4.2) eslint-config-prettier: specifier: ^10.1.2 - version: 10.1.2(eslint@9.12.0(jiti@2.4.2)) + version: 10.1.2(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-prettier: specifier: ^5.2.6 - version: 5.2.6(eslint-config-prettier@10.1.2(eslint@9.12.0(jiti@2.4.2)))(eslint@9.12.0(jiti@2.4.2))(prettier@3.3.2) + version: 5.2.6(eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2) eslint-plugin-storybook: specifier: ^9.1.1 - version: 9.1.1(eslint@9.12.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + version: 9.1.1(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2)) + version: 4.1.4(@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-vue: specifier: ^9.27.0 - version: 9.27.0(eslint@9.12.0(jiti@2.4.2)) + version: 9.27.0(eslint@9.35.0(jiti@2.4.2)) fs-extra: specifier: ^11.2.0 version: 11.2.0 @@ -307,8 +307,8 @@ importers: specifier: ^5.4.5 version: 5.9.2 typescript-eslint: - specifier: ^8.0.0 - version: 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) + specifier: ^8.42.0 + version: 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) unplugin-icons: specifier: ^0.22.0 version: 0.22.0(@vue/compiler-sfc@3.5.13) @@ -960,8 +960,8 @@ packages: resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.3': - resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': @@ -1314,32 +1314,50 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/eslint-utils@4.8.0': + resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.18.0': - resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.6.0': - resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.12.0': resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.4': - resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + '@eslint/js@9.35.0': + resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.3': - resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@firebase/analytics-compat@0.2.18': @@ -1561,20 +1579,20 @@ packages: engines: {node: '>=6'} hasBin: true - '@humanfs/core@0.19.0': - resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.5': - resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} '@iconify/json@2.2.380': @@ -2510,9 +2528,6 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2594,26 +2609,20 @@ packages: '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} - '@typescript-eslint/eslint-plugin@8.0.0': - resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + '@typescript-eslint/eslint-plugin@8.42.0': + resolution: {integrity: sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.42.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.0.0': - resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + '@typescript-eslint/parser@8.42.0': + resolution: {integrity: sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/project-service@8.39.0': resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==} @@ -2621,45 +2630,46 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.0.0': - resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + '@typescript-eslint/project-service@8.42.0': + resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/scope-manager@8.39.0': resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.42.0': + resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.39.0': resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.0.0': - resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + '@typescript-eslint/tsconfig-utils@8.42.0': + resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.0.0': - resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + '@typescript-eslint/type-utils@8.42.0': + resolution: {integrity: sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/types@8.39.0': resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.0.0': - resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + '@typescript-eslint/types@8.42.0': + resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true '@typescript-eslint/typescript-estree@8.39.0': resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==} @@ -2667,11 +2677,11 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.0.0': - resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + '@typescript-eslint/typescript-estree@8.42.0': + resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/utils@8.39.0': resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==} @@ -2680,14 +2690,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.0.0': - resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + '@typescript-eslint/utils@8.42.0': + resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/visitor-keys@8.39.0': resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.42.0': + resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-vue@5.1.4': resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2898,6 +2915,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} @@ -3000,10 +3022,6 @@ packages: resolution: {integrity: sha512-yOzOZcR9Tn7enTF66bqKorGGH0F36vcPaSWg8fO0c0UYb3LX3VMXj5ZxEqQLNOecAhlRJ7wYZja5i4jTlnbIfQ==} engines: {node: '>=4'} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3490,10 +3508,6 @@ packages: diff-match-patch@1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dirty-json@0.9.2: resolution: {integrity: sha512-7SCDfnQtBObcngVXNPZcnxGxqqPTK4UqeXeKAch+RGH5qpqadWbV9FmN71x9Bb4tTs0TNFb4FT/4Kz4P4Cjqcw==} engines: {node: '>=6.0.0'} @@ -3731,8 +3745,8 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -3743,8 +3757,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.12.0: - resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==} + eslint@9.35.0: + resolution: {integrity: sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3760,6 +3774,10 @@ packages: resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3917,9 +3935,6 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -4059,10 +4074,6 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4177,6 +4188,10 @@ packages: resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -5685,10 +5700,6 @@ packages: resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -5879,9 +5890,6 @@ packages: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - three@0.170.0: resolution: {integrity: sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==} @@ -5958,12 +5966,6 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -6005,14 +6007,12 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - typescript-eslint@8.0.0: - resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + typescript-eslint@8.42.0: + resolution: {integrity: sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' typescript@5.4.2: resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} @@ -7412,7 +7412,7 @@ snapshots: '@babel/runtime@7.27.6': {} - '@babel/runtime@7.28.3': {} + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': dependencies: @@ -7618,22 +7618,31 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.12.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.35.0(jiti@2.4.2))': dependencies: - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} - - '@eslint/config-array@0.18.0': + '@eslint-community/eslint-utils@4.8.0(eslint@9.35.0(jiti@2.4.2))': dependencies: - '@eslint/object-schema': 2.1.4 + eslint: 9.35.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.6.0': {} + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.1.0': dependencies: @@ -7649,12 +7658,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@9.12.0': {} - '@eslint/object-schema@2.1.4': {} + '@eslint/js@9.35.0': {} - '@eslint/plugin-kit@0.2.3': + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': dependencies: + '@eslint/core': 0.15.2 levn: 0.4.1 '@firebase/analytics-compat@0.2.18(@firebase/app-compat@0.2.53)(@firebase/app@0.11.4)': @@ -7987,16 +8013,16 @@ snapshots: protobufjs: 7.5.0 yargs: 17.7.2 - '@humanfs/core@0.19.0': {} + '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.5': + '@humanfs/node@0.16.7': dependencies: - '@humanfs/core': 0.19.0 - '@humanwhocodes/retry': 0.3.1 + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} + '@humanwhocodes/retry@0.4.3': {} '@iconify/json@2.2.380': dependencies: @@ -8027,14 +8053,14 @@ snapshots: '@intlify/message-compiler': 9.14.3 '@intlify/shared': 9.14.3 - '@intlify/eslint-plugin-vue-i18n@3.2.0(eslint@9.12.0(jiti@2.4.2))': + '@intlify/eslint-plugin-vue-i18n@3.2.0(eslint@9.35.0(jiti@2.4.2))': dependencies: '@eslint/eslintrc': 3.1.0 '@intlify/core-base': 9.14.3 '@intlify/message-compiler': 9.14.3 debug: 4.4.1 - eslint: 9.12.0(jiti@2.4.2) - eslint-compat-utils: 0.6.5(eslint@9.12.0(jiti@2.4.2)) + eslint: 9.35.0(jiti@2.4.2) + eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.4.2)) glob: 10.4.5 globals: 15.15.0 ignore: 6.0.2 @@ -8047,7 +8073,7 @@ snapshots: parse5: 7.1.2 semver: 7.7.2 synckit: 0.9.3 - vue-eslint-parser: 9.4.3(eslint@9.12.0(jiti@2.4.2)) + vue-eslint-parser: 9.4.3(eslint@9.35.0(jiti@2.4.2)) yaml-eslint-parser: 1.3.0 transitivePeerDependencies: - supports-color @@ -8244,10 +8270,10 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@nx/cypress@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)': + '@nx/cypress@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)': dependencies: '@nx/devkit': 21.4.1(nx@21.4.1) - '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1) + '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1) '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) detect-port: 1.6.1 @@ -8278,11 +8304,11 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/eslint@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)': + '@nx/eslint@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)': dependencies: '@nx/devkit': 21.4.1(nx@21.4.1) '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) semver: 7.7.2 tslib: 2.8.1 typescript: 5.8.3 @@ -8366,10 +8392,10 @@ snapshots: '@nx/nx-win32-x64-msvc@21.4.1': optional: true - '@nx/playwright@21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)': + '@nx/playwright@21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)': dependencies: '@nx/devkit': 21.4.1(nx@21.4.1) - '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1) + '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1) '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) minimatch: 9.0.3 @@ -8388,11 +8414,11 @@ snapshots: - typescript - verdaccio - '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': + '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': dependencies: - '@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) + '@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/devkit': 21.4.1(nx@21.4.1) - '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.12.0(jiti@2.4.2))(nx@21.4.1) + '@nx/eslint': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1) '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) semver: 7.7.2 @@ -8880,7 +8906,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -9098,8 +9124,6 @@ snapshots: '@types/estree@1.0.5': {} - '@types/estree@1.0.6': {} - '@types/estree@1.0.8': {} '@types/fs-extra@11.0.4': @@ -9188,33 +9212,31 @@ snapshots: '@types/webxr@0.5.20': {} - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/type-utils': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/utils': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.0.0 - eslint: 9.12.0(jiti@2.4.2) + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/type-utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 + eslint: 9.35.0(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.9.2) - optionalDependencies: + ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 debug: 4.4.1 - eslint: 9.12.0(jiti@2.4.2) - optionalDependencies: + eslint: 9.35.0(jiti@2.4.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -9228,50 +9250,48 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.0.0': + '@typescript-eslint/project-service@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + debug: 4.4.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@8.39.0': dependencies: '@typescript-eslint/types': 8.39.0 '@typescript-eslint/visitor-keys': 8.39.0 + '@typescript-eslint/scope-manager@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) debug: 4.4.1 - ts-api-utils: 1.3.0(typescript@5.9.2) - optionalDependencies: + eslint: 9.35.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - - eslint - supports-color - '@typescript-eslint/types@8.0.0': {} - '@typescript-eslint/types@8.39.0': {} - '@typescript-eslint/typescript-estree@8.0.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 - debug: 4.4.1 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 1.3.0(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types@8.42.0': {} '@typescript-eslint/typescript-estree@8.39.0(typescript@5.9.2)': dependencies: @@ -9289,38 +9309,54 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.12.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.9.2) - eslint: 9.12.0(jiti@2.4.2) - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/utils@8.39.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.12.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.39.0 - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) - eslint: 9.12.0(jiti@2.4.2) + '@typescript-eslint/project-service': 8.42.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.0.0': + '@typescript-eslint/utils@8.39.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.0.0 - eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color '@typescript-eslint/visitor-keys@8.39.0': dependencies: '@typescript-eslint/types': 8.39.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 + eslint-visitor-keys: 4.2.1 + '@vitejs/plugin-vue@5.1.4(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': dependencies: vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) @@ -9624,10 +9660,16 @@ snapshots: dependencies: acorn: 8.14.1 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn@7.4.1: {} acorn@8.14.1: {} + acorn@8.15.0: {} + address@1.2.2: {} agent-base@7.1.4: {} @@ -9737,8 +9779,6 @@ snapshots: arr-rotate@1.0.0: {} - array-union@2.1.0: {} - asap@2.0.6: {} assert-never@1.4.0: {} @@ -10221,10 +10261,6 @@ snapshots: diff-match-patch@1.0.5: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - dirty-json@0.9.2: dependencies: lex: 1.7.9 @@ -10430,49 +10466,49 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.6.5(eslint@9.12.0(jiti@2.4.2)): + eslint-compat-utils@0.6.5(eslint@9.35.0(jiti@2.4.2)): dependencies: - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) semver: 7.7.2 - eslint-config-prettier@10.1.2(eslint@9.12.0(jiti@2.4.2)): + eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)): dependencies: - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) - eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.2(eslint@9.12.0(jiti@2.4.2)))(eslint@9.12.0(jiti@2.4.2))(prettier@3.3.2): + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2): dependencies: - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) prettier: 3.3.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.3 optionalDependencies: - eslint-config-prettier: 10.1.2(eslint@9.12.0(jiti@2.4.2)) + eslint-config-prettier: 10.1.2(eslint@9.35.0(jiti@2.4.2)) - eslint-plugin-storybook@9.1.1(eslint@9.12.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): + eslint-plugin-storybook@9.1.1(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 8.39.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - eslint: 9.12.0(jiti@2.4.2) + '@typescript-eslint/utils': 8.39.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)): dependencies: - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - eslint-plugin-vue@9.27.0(eslint@9.12.0(jiti@2.4.2)): + eslint-plugin-vue@9.27.0(eslint@9.35.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.12.0(jiti@2.4.2)) - eslint: 9.12.0(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.4.2)) + eslint: 9.35.0(jiti@2.4.2) globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.0 semver: 7.7.2 - vue-eslint-parser: 9.4.3(eslint@9.12.0(jiti@2.4.2)) + vue-eslint-parser: 9.4.3(eslint@9.35.0(jiti@2.4.2)) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -10482,7 +10518,7 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.1.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -10491,28 +10527,29 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.12.0(jiti@2.4.2): + eslint@9.35.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.12.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.11.0 - '@eslint/config-array': 0.18.0 - '@eslint/core': 0.6.0 - '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.12.0 - '@eslint/plugin-kit': 0.2.3 - '@humanfs/node': 0.16.5 + '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.35.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.1 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 + eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 - espree: 10.2.0 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -10527,7 +10564,6 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - text-table: 0.2.0 optionalDependencies: jiti: 2.4.2 transitivePeerDependencies: @@ -10541,6 +10577,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.1 + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + espree@9.6.1: dependencies: acorn: 8.14.1 @@ -10745,13 +10787,11 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.3.1: {} - flatted@3.3.3: {} follow-redirects@1.15.6: {} @@ -10894,15 +10934,6 @@ snapshots: globals@15.15.0: {} - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.2.0: {} gpt-tokenizer@2.9.0: {} @@ -11006,6 +11037,8 @@ snapshots: ignore@6.0.2: {} + ignore@7.0.5: {} + immediate@3.0.6: {} import-fresh@3.3.0: @@ -12841,8 +12874,6 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 - slash@3.0.0: {} - slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -13047,8 +13078,6 @@ snapshots: glob: 10.4.5 minimatch: 9.0.5 - text-table@0.2.0: {} - three@0.170.0: {} tiny-invariant@1.3.3: {} @@ -13108,10 +13137,6 @@ snapshots: trough@2.2.0: {} - ts-api-utils@1.3.0(typescript@5.9.2): - dependencies: - typescript: 5.9.2 - ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -13145,15 +13170,15 @@ snapshots: type-fest@4.41.0: {} - typescript-eslint@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2): + typescript-eslint@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/parser': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/utils': 8.0.0(eslint@9.12.0(jiti@2.4.2))(typescript@5.9.2) - optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) typescript: 5.9.2 transitivePeerDependencies: - - eslint - supports-color typescript@5.4.2: {} @@ -13503,10 +13528,10 @@ snapshots: vue: 3.5.13(typescript@5.9.2) vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.13(typescript@5.9.2)) - vue-eslint-parser@9.4.3(eslint@9.12.0(jiti@2.4.2)): + vue-eslint-parser@9.4.3(eslint@9.35.0(jiti@2.4.2)): dependencies: debug: 4.4.1 - eslint: 9.12.0(jiti@2.4.2) + eslint: 9.35.0(jiti@2.4.2) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 diff --git a/src/App.vue b/src/App.vue index 1a4068a27..5e183c430 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,10 +15,10 @@ import ProgressSpinner from 'primevue/progressspinner' import { computed, onMounted } from 'vue' import GlobalDialog from '@/components/dialog/GlobalDialog.vue' -import { useConflictDetection } from '@/composables/useConflictDetection' import config from '@/config' import { useWorkspaceStore } from '@/stores/workspaceStore' +import { useConflictDetection } from './composables/useConflictDetection' import { electronAPI, isElectron } from './utils/envUtil' const workspaceStore = useWorkspaceStore() diff --git a/src/components/breadcrumb/SubgraphBreadcrumb.vue b/src/components/breadcrumb/SubgraphBreadcrumb.vue index af6324aa9..e710f6c82 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumb.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumb.vue @@ -40,6 +40,7 @@ import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbIt import { useOverflowObserver } from '@/composables/element/useOverflowObserver' import { useCanvasStore } from '@/stores/graphStore' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' +import { useSubgraphStore } from '@/stores/subgraphStore' import { useWorkflowStore } from '@/stores/workflowStore' import { forEachSubgraphNode } from '@/utils/graphTraversalUtil' @@ -52,6 +53,9 @@ const workflowStore = useWorkflowStore() const navigationStore = useSubgraphNavigationStore() const breadcrumbRef = ref>() const workflowName = computed(() => workflowStore.activeWorkflow?.filename) +const isBlueprint = computed(() => + useSubgraphStore().isSubgraphBlueprint(workflowStore.activeWorkflow) +) const collapseTabs = ref(false) const overflowingTabs = ref(false) @@ -89,6 +93,7 @@ const home = computed(() => ({ label: workflowName.value, icon: 'pi pi-home', key: 'root', + isBlueprint: isBlueprint.value, command: () => { const canvas = useCanvasStore().getCanvas() if (!canvas.graph) throw new TypeError('Canvas has no graph') diff --git a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue index b980c0e2d..eb688dff6 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue @@ -16,6 +16,7 @@ @click="handleClick" > {{ item.label }} + (() => { command: async () => { await workflowService.duplicateWorkflow(workflowStore.activeWorkflow!) }, - visible: isRoot + visible: isRoot && !props.item.isBlueprint }, { separator: true, @@ -153,12 +155,26 @@ const menuItems = computed(() => { await useCommandStore().execute('Comfy.ClearWorkflow') } }, + { + separator: true, + visible: props.item.key === 'root' && props.item.isBlueprint + }, + { + label: t('subgraphStore.publish'), + icon: 'pi pi-copy', + command: async () => { + await workflowService.saveWorkflowAs(workflowStore.activeWorkflow!) + }, + visible: props.item.key === 'root' && props.item.isBlueprint + }, { separator: true, visible: isRoot }, { - label: t('breadcrumbsMenu.deleteWorkflow'), + label: props.item.isBlueprint + ? t('breadcrumbsMenu.deleteBlueprint') + : t('breadcrumbsMenu.deleteWorkflow'), icon: 'pi pi-times', command: async () => { await workflowService.deleteWorkflow(workflowStore.activeWorkflow!) diff --git a/src/components/common/FormImageUpload.vue b/src/components/common/FormImageUpload.vue index 1515289cc..efb09c900 100644 --- a/src/components/common/FormImageUpload.vue +++ b/src/components/common/FormImageUpload.vue @@ -3,7 +3,7 @@ + + + {{ + t('missingModelsDialog.doNotAskAgain') + }} + + import Button from 'primevue/button' +import Checkbox from 'primevue/checkbox' import Message from 'primevue/message' +import { ref } from 'vue' +import { useI18n } from 'vue-i18n' import type { ConfirmationDialogType } from '@/services/dialogService' import { useDialogStore } from '@/stores/dialogStore' +import { useSettingStore } from '@/stores/settingStore' const props = defineProps<{ message: string @@ -87,14 +106,20 @@ const props = defineProps<{ hint?: string }>() +const { t } = useI18n() + const onCancel = () => useDialogStore().closeDialog() +const doNotAskAgain = ref(false) + const onDeny = () => { props.onConfirm(false) useDialogStore().closeDialog() } const onConfirm = () => { + if (props.type === 'overwriteBlueprint' && doNotAskAgain.value) + void useSettingStore().set('Comfy.Workflow.WarnBlueprintOverwrite', false) props.onConfirm(true) useDialogStore().closeDialog() } diff --git a/src/components/dialog/content/ErrorDialogContent.vue b/src/components/dialog/content/ErrorDialogContent.vue index 4f35511cf..5b732ebbc 100644 --- a/src/components/dialog/content/ErrorDialogContent.vue +++ b/src/components/dialog/content/ErrorDialogContent.vue @@ -105,7 +105,7 @@ const showContactSupport = async () => { onMounted(async () => { if (!systemStatsStore.systemStats) { - await systemStatsStore.fetchSystemStats() + await systemStatsStore.refetchSystemStats() } try { diff --git a/src/components/dialog/content/LoadWorkflowWarning.vue b/src/components/dialog/content/LoadWorkflowWarning.vue index 216e54dd0..3bd571179 100644 --- a/src/components/dialog/content/LoadWorkflowWarning.vue +++ b/src/components/dialog/content/LoadWorkflowWarning.vue @@ -54,19 +54,12 @@ import Button from 'primevue/button' import ListBox from 'primevue/listbox' import { computed } from 'vue' -import { useI18n } from 'vue-i18n' import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue' import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue' import { useMissingNodes } from '@/composables/nodePack/useMissingNodes' -import { useDialogService } from '@/services/dialogService' +import { useManagerState } from '@/composables/useManagerState' import { useComfyManagerStore } from '@/stores/comfyManagerStore' -import { useCommandStore } from '@/stores/commandStore' -import { - ManagerUIState, - useManagerStateStore -} from '@/stores/managerStateStore' -import { useToastStore } from '@/stores/toastStore' import type { MissingNodeType } from '@/types/comfy' import { ManagerTab } from '@/types/comfyManagerTypes' @@ -81,6 +74,7 @@ const { missingNodePacks, isLoading, error, missingCoreNodes } = useMissingNodes() const comfyManagerStore = useComfyManagerStore() +const managerState = useManagerState() // Check if any of the missing packs are currently being installed const isInstalling = computed(() => { @@ -111,47 +105,21 @@ const uniqueNodes = computed(() => { }) }) -const managerStateStore = useManagerStateStore() - // Show manager buttons unless manager is disabled const showManagerButtons = computed(() => { - return managerStateStore.managerUIState !== ManagerUIState.DISABLED + return managerState.shouldShowManagerButtons.value }) // Only show Install All button for NEW_UI (new manager with v4 support) const showInstallAllButton = computed(() => { - return managerStateStore.managerUIState === ManagerUIState.NEW_UI + return managerState.shouldShowInstallButton.value }) const openManager = async () => { - const state = managerStateStore.managerUIState - - switch (state) { - case ManagerUIState.DISABLED: - useDialogService().showSettingsDialog('extension') - break - - case ManagerUIState.LEGACY_UI: - try { - await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility') - } catch { - // If legacy command doesn't exist, show toast - const { t } = useI18n() - useToastStore().add({ - severity: 'error', - summary: t('g.error'), - detail: t('manager.legacyMenuNotAvailable'), - life: 3000 - }) - } - break - - case ManagerUIState.NEW_UI: - useDialogService().showManagerDialog({ - initialTab: ManagerTab.Missing - }) - break - } + await managerState.openManager({ + initialTab: ManagerTab.Missing, + showToastOnLegacyError: true + }) } diff --git a/src/components/dialog/content/MissingCoreNodesMessage.vue b/src/components/dialog/content/MissingCoreNodesMessage.vue index 036f088b3..cf81441f1 100644 --- a/src/components/dialog/content/MissingCoreNodesMessage.vue +++ b/src/components/dialog/content/MissingCoreNodesMessage.vue @@ -42,9 +42,8 @@ diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 467da7e5d..c6eb86404 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -33,8 +33,8 @@ @@ -44,7 +44,6 @@ :node-data="nodeData" :position="nodePositions.get(nodeData.id)" :size="nodeSizes.get(nodeData.id)" - :selected="nodeData.selected" :readonly="false" :executing="executionStore.executingNodeId === nodeData.id" :error=" @@ -79,6 +78,7 @@ import { computed, onMounted, onUnmounted, + provide, ref, shallowRef, watch, @@ -89,21 +89,16 @@ import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitter import BottomPanel from '@/components/bottomPanel/BottomPanel.vue' import DomWidgets from '@/components/graph/DomWidgets.vue' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' -import MiniMap from '@/components/graph/MiniMap.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' import SelectionToolbox from '@/components/graph/SelectionToolbox.vue' import TitleEditor from '@/components/graph/TitleEditor.vue' -import TransformPane from '@/components/graph/TransformPane.vue' import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue' import SideToolbar from '@/components/sidebar/SideToolbar.vue' import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue' -import { useTransformState } from '@/composables/element/useTransformState' import { useChainCallback } from '@/composables/functional/useChainCallback' -import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' -import type { - NodeState, - VueNodeData -} from '@/composables/graph/useGraphNodeManager' +import { useNodeEventHandlers } from '@/composables/graph/useNodeEventHandlers' +import { useViewportCulling } from '@/composables/graph/useViewportCulling' +import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' import { useNodeBadge } from '@/composables/node/useNodeBadge' import { useCanvasDrop } from '@/composables/useCanvasDrop' import { useContextMenuTranslation } from '@/composables/useContextMenuTranslation' @@ -116,13 +111,10 @@ import { useWorkflowAutoSave } from '@/composables/useWorkflowAutoSave' import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence' import { CORE_SETTINGS } from '@/constants/coreSettings' import { i18n, t } from '@/i18n' -import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph' -import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync' -import { useLinkLayoutSync } from '@/renderer/core/layout/sync/useLinkLayoutSync' -import { useSlotLayoutSync } from '@/renderer/core/layout/sync/useSlotLayoutSync' -import { LayoutSource } from '@/renderer/core/layout/types' +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys' +import TransformPane from '@/renderer/core/layout/TransformPane.vue' +import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue' import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue' import { UnauthorizedError, api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' @@ -155,7 +147,6 @@ const workspaceStore = useWorkspaceStore() const canvasStore = useCanvasStore() const executionStore = useExecutionStore() const toastStore = useToastStore() -const layoutMutations = useLayoutMutations() const betaMenuEnabled = computed( () => settingStore.get('Comfy.UseNewMenu') !== 'Disabled' ) @@ -172,280 +163,43 @@ const selectionToolboxEnabled = computed(() => const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible')) -// Feature flags (Vue-related) +// Feature flags const { shouldRenderVueNodes } = useVueFeatureFlags() - const isVueNodesEnabled = computed(() => shouldRenderVueNodes.value) -// Vue node lifecycle management - initialize after graph is ready -let nodeManager: ReturnType | null = null -let cleanupNodeManager: (() => void) | null = null - -// Slot layout sync management -let slotSync: ReturnType | null = null -let linkSync: ReturnType | null = null -const vueNodeData = ref>(new Map()) -const nodeState = ref>(new Map()) -const nodePositions = ref>( - new Map() +// Vue node system +const vueNodeLifecycle = useVueNodeLifecycle(isVueNodesEnabled) +const viewportCulling = useViewportCulling( + isVueNodesEnabled, + vueNodeLifecycle.vueNodeData, + vueNodeLifecycle.nodeDataTrigger, + vueNodeLifecycle.nodeManager ) -const nodeSizes = ref>( - new Map() -) -let detectChangesInRAF = () => {} +const nodeEventHandlers = useNodeEventHandlers(vueNodeLifecycle.nodeManager) -// Initialize node manager when graph becomes available -// Add a reactivity trigger to force computed re-evaluation -const nodeDataTrigger = ref(0) - -const initializeNodeManager = () => { - if (!comfyApp.graph || nodeManager) return - nodeManager = useGraphNodeManager(comfyApp.graph) - cleanupNodeManager = nodeManager.cleanup - // Use the manager's reactive maps directly - vueNodeData.value = nodeManager.vueNodeData - nodeState.value = nodeManager.nodeState - nodePositions.value = nodeManager.nodePositions - nodeSizes.value = nodeManager.nodeSizes - detectChangesInRAF = nodeManager.detectChangesInRAF - - // Initialize layout system with existing nodes - const nodes = comfyApp.graph._nodes.map((node: any) => ({ - id: node.id.toString(), - pos: node.pos, - size: node.size - })) - layoutStore.initializeFromLiteGraph(nodes) - - // Seed reroutes into the Layout Store so hit-testing uses the new path - for (const reroute of comfyApp.graph.reroutes.values()) { - const [x, y] = reroute.pos - const parent = reroute.parentId ?? undefined - const linkIds = Array.from(reroute.linkIds) - layoutMutations.createReroute(reroute.id, { x, y }, parent, linkIds) - } - - // Seed existing links into the Layout Store (topology only) - for (const link of comfyApp.graph._links.values()) { - layoutMutations.createLink( - link.id, - link.origin_id, - link.origin_slot, - link.target_id, - link.target_slot - ) - } - - // Initialize layout sync (one-way: Layout Store → LiteGraph) - const { startSync } = useLayoutSync() - startSync(canvasStore.canvas) - - // Initialize slot layout sync for hit detection - slotSync = useSlotLayoutSync() - if (canvasStore.canvas) { - slotSync.start(canvasStore.canvas as LGraphCanvas) - } - - // Initialize link layout sync for event-driven updates - linkSync = useLinkLayoutSync() - if (canvasStore.canvas) { - linkSync.start(canvasStore.canvas as LGraphCanvas) - } - - // Force computed properties to re-evaluate - nodeDataTrigger.value++ -} - -const disposeNodeManagerAndSyncs = () => { - if (!nodeManager) return - try { - cleanupNodeManager?.() - } catch { - /* empty */ - } - nodeManager = null - cleanupNodeManager = null - - // Clean up slot layout sync - if (slotSync) { - slotSync.stop() - slotSync = null - } - - // Clean up link layout sync - if (linkSync) { - linkSync.stop() - linkSync = null - } - - // Reset reactive maps to inert defaults - vueNodeData.value = new Map() - nodeState.value = new Map() - nodePositions.value = new Map() - nodeSizes.value = new Map() -} - -// Watch for transformPaneEnabled to gate the node manager lifecycle -watch( - () => isVueNodesEnabled.value && Boolean(comfyApp.graph), - (enabled) => { - if (enabled) { - initializeNodeManager() - } else { - disposeNodeManagerAndSyncs() - } - }, - { immediate: true } -) - -// Transform state for viewport culling -const { syncWithCanvas } = useTransformState() - -const nodesToRender = computed(() => { - // Early return for zero overhead when Vue nodes are disabled - if (!isVueNodesEnabled.value) { - return [] - } - - // Access trigger to force re-evaluation after nodeManager initialization - void nodeDataTrigger.value - - if (!comfyApp.graph) { - return [] - } - - const allNodes = Array.from(vueNodeData.value.values()) - - // Apply viewport culling - check if node bounds intersect with viewport - if (nodeManager && canvasStore.canvas && comfyApp.canvas) { - const canvas = canvasStore.canvas - const manager = nodeManager - - // Ensure transform is synced before checking visibility - syncWithCanvas(comfyApp.canvas) - - const ds = canvas.ds - - // Work in screen space - viewport is simply the canvas element size - const viewport_width = canvas.canvas.width - const viewport_height = canvas.canvas.height - - // Add margin that represents a constant distance in canvas space - // Convert canvas units to screen pixels by multiplying by scale - const canvasMarginDistance = 200 // Fixed margin in canvas units - const margin_x = canvasMarginDistance * ds.scale - const margin_y = canvasMarginDistance * ds.scale - - const filtered = allNodes.filter((nodeData) => { - const node = manager.getNode(nodeData.id) - if (!node) return false - - // Transform node position to screen space (same as DOM widgets) - const screen_x = (node.pos[0] + ds.offset[0]) * ds.scale - const screen_y = (node.pos[1] + ds.offset[1]) * ds.scale - const screen_width = node.size[0] * ds.scale - const screen_height = node.size[1] * ds.scale - - // Check if node bounds intersect with expanded viewport (in screen space) - const isVisible = !( - screen_x + screen_width < -margin_x || - screen_x > viewport_width + margin_x || - screen_y + screen_height < -margin_y || - screen_y > viewport_height + margin_y - ) - - return isVisible - }) - - return filtered - } - - return allNodes -}) - -let lastScale = 1 -let lastOffsetX = 0 -let lastOffsetY = 0 +const nodePositions = vueNodeLifecycle.nodePositions +const nodeSizes = vueNodeLifecycle.nodeSizes +const nodesToRender = viewportCulling.nodesToRender const handleTransformUpdate = () => { - // Skip all work if Vue nodes are disabled - if (!isVueNodesEnabled.value) { - return - } - - // Sync transform state only when it changes (avoids reflows) - if (comfyApp.canvas?.ds) { - const currentScale = comfyApp.canvas.ds.scale - const currentOffsetX = comfyApp.canvas.ds.offset[0] - const currentOffsetY = comfyApp.canvas.ds.offset[1] - - if ( - currentScale !== lastScale || - currentOffsetX !== lastOffsetX || - currentOffsetY !== lastOffsetY - ) { - syncWithCanvas(comfyApp.canvas) - lastScale = currentScale - lastOffsetX = currentOffsetX - lastOffsetY = currentOffsetY - } - } - - // Detect node changes during transform updates - detectChangesInRAF() - - // Trigger reactivity for nodesToRender - void nodesToRender.value.length + viewportCulling.handleTransformUpdate( + vueNodeLifecycle.detectChangesInRAF.value + ) } +const handleNodeSelect = nodeEventHandlers.handleNodeSelect +const handleNodeCollapse = nodeEventHandlers.handleNodeCollapse +const handleNodeTitleUpdate = nodeEventHandlers.handleNodeTitleUpdate -// Node event handlers -const handleNodeSelect = (event: PointerEvent, nodeData: VueNodeData) => { - if (!canvasStore.canvas || !nodeManager) return - - const node = nodeManager.getNode(nodeData.id) - if (!node) return - - if (!event.ctrlKey && !event.metaKey) { - canvasStore.canvas.deselectAllNodes() - } - - canvasStore.canvas.selectNode(node) - - // Bring node to front when clicked (similar to LiteGraph behavior) - // Skip if node is pinned - if (!node.flags?.pinned) { - layoutMutations.setSource(LayoutSource.Vue) - layoutMutations.bringNodeToFront(nodeData.id) - } - node.selected = true - - canvasStore.updateSelectedItems() -} - -// Handle node collapse state changes -const handleNodeCollapse = (nodeId: string, collapsed: boolean) => { - if (!nodeManager) return - - const node = nodeManager.getNode(nodeId) - if (!node) return - - // Use LiteGraph's collapse method if the state needs to change - const currentCollapsed = node.flags?.collapsed ?? false - if (currentCollapsed !== collapsed) { - node.collapse() - } -} - -// Handle node title updates -const handleNodeTitleUpdate = (nodeId: string, newTitle: string) => { - if (!nodeManager) return - - const node = nodeManager.getNode(nodeId) - if (!node) return - - // Update the node title in LiteGraph for persistence - node.title = newTitle -} +// Provide selection state to all Vue nodes +const selectedNodeIds = computed( + () => + new Set( + canvasStore.selectedItems + .filter((item) => item.id !== undefined) + .map((item) => String(item.id)) + ) +) +provide(SelectedNodeIdsKey, selectedNodeIds) watchEffect(() => { nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated') @@ -653,29 +407,7 @@ onMounted(async () => { comfyAppReady.value = true - // Set up Vue node initialization only when enabled - if (isVueNodesEnabled.value) { - // Set up a one-time listener for when the first node is added - // This handles the case where Vue nodes are enabled but the graph starts empty - // TODO: Replace this with a reactive graph mutations observer when available - if (comfyApp.graph && !nodeManager && comfyApp.graph._nodes.length === 0) { - const originalOnNodeAdded = comfyApp.graph.onNodeAdded - comfyApp.graph.onNodeAdded = function (node: any) { - // Restore original handler - comfyApp.graph.onNodeAdded = originalOnNodeAdded - - // Initialize node manager if needed - if (isVueNodesEnabled.value && !nodeManager) { - initializeNodeManager() - } - - // Call original handler - if (originalOnNodeAdded) { - originalOnNodeAdded.call(this, node) - } - } - } - } + vueNodeLifecycle.setupEmptyGraphListener() comfyApp.canvas.onSelectionChange = useChainCallback( comfyApp.canvas.onSelectionChange, @@ -719,17 +451,6 @@ onMounted(async () => { }) onUnmounted(() => { - if (nodeManager) { - nodeManager.cleanup() - nodeManager = null - } - if (slotSync) { - slotSync.stop() - slotSync = null - } - if (linkSync) { - linkSync.stop() - linkSync = null - } + vueNodeLifecycle.cleanup() }) diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue index 38f0a2c0d..8ce9353aa 100644 --- a/src/components/graph/SelectionToolbox.vue +++ b/src/components/graph/SelectionToolbox.vue @@ -2,12 +2,12 @@ commandStore.execute('Comfy.PublishSubgraph')" + > + + + + + + + diff --git a/src/components/helpcenter/HelpCenterMenuContent.vue b/src/components/helpcenter/HelpCenterMenuContent.vue index 852b1b593..c91589407 100644 --- a/src/components/helpcenter/HelpCenterMenuContent.vue +++ b/src/components/helpcenter/HelpCenterMenuContent.vue @@ -142,11 +142,12 @@ import { useI18n } from 'vue-i18n' import PuzzleIcon from '@/components/icons/PuzzleIcon.vue' import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment' -import { useDialogService } from '@/services/dialogService' +import { useManagerState } from '@/composables/useManagerState' import { type ReleaseNote } from '@/services/releaseService' import { useCommandStore } from '@/stores/commandStore' import { useReleaseStore } from '@/stores/releaseStore' import { useSettingStore } from '@/stores/settingStore' +import { ManagerTab } from '@/types/comfyManagerTypes' import { electronAPI, isElectron } from '@/utils/envUtil' import { formatVersionAnchor } from '@/utils/formatUtil' @@ -191,7 +192,6 @@ const { t, locale } = useI18n() const releaseStore = useReleaseStore() const commandStore = useCommandStore() const settingStore = useSettingStore() -const dialogService = useDialogService() // Emits const emit = defineEmits<{ @@ -313,8 +313,11 @@ const menuItems = computed(() => { icon: PuzzleIcon, label: t('helpCenter.managerExtension'), showRedDot: shouldShowManagerRedDot.value, - action: () => { - dialogService.showManagerDialog() + action: async () => { + await useManagerState().openManager({ + initialTab: ManagerTab.All, + showToastOnLegacyError: false + }) emit('close') } }, diff --git a/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue b/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue index 1f1d782f3..2dc099c41 100644 --- a/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue +++ b/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue @@ -1,6 +1,6 @@ - + - + + + + + + + + + + + @@ -332,6 +299,18 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {