diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml index f8f6cf955..eaaaefee0 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/test-ui.yaml @@ -229,7 +229,13 @@ jobs: - name: Run Playwright tests (${{ matrix.browser }}) id: playwright - run: npx playwright test --project=${{ matrix.browser }} --reporter=html + run: | + # Run tests with both HTML and JSON reporters + PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ + npx playwright test --project=${{ matrix.browser }} \ + --reporter=list \ + --reporter=html \ + --reporter=json working-directory: ComfyUI_frontend - uses: actions/upload-artifact@v4 @@ -275,7 +281,12 @@ jobs: merge-multiple: true - name: Merge into HTML Report - run: npx playwright merge-reports --reporter html ./all-blob-reports + run: | + # Generate HTML report + npx playwright merge-reports --reporter=html ./all-blob-reports + # Generate JSON report separately with explicit output path + PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ + npx playwright merge-reports --reporter=json ./all-blob-reports working-directory: ComfyUI_frontend - name: Upload HTML report diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..ae90f7051 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +ignore-workspace-root-check=true diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png index 719ec65e4..ce88325aa 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png index b880fca84..bf1d18934 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png differ diff --git a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png index 474fbb555..3f3d831bb 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png differ diff --git a/components.json b/components.json new file mode 100644 index 000000000..5526f900d --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-vue.com/schema.json", + "style": "new-york", + "typescript": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/assets/css/style.css", + "baseColor": "stone", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "composables": "@/composables", + "utils": "@/utils", + "ui": "@/components/ui", + "lib": "@/lib" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/docs/adr/0004-fork-primevue-ui-library.md b/docs/adr/0004-fork-primevue-ui-library.md new file mode 100644 index 000000000..02bd18736 --- /dev/null +++ b/docs/adr/0004-fork-primevue-ui-library.md @@ -0,0 +1,62 @@ +# 4. Fork PrimeVue UI Library + +Date: 2025-08-27 + +## Status + +Rejected + +## Context + +ComfyUI's frontend requires modifications to PrimeVue components that cannot be achieved through the library's customization APIs. Two specific technical incompatibilities have been identified with the transform-based canvas architecture: + +**Screen Coordinate Hit-Testing Conflicts:** +- PrimeVue components use `getBoundingClientRect()` for screen coordinate calculations that don't account for CSS transforms +- The Slider component directly uses raw `pageX/pageY` coordinates ([lines 102-103](https://github.com/primefaces/primevue/blob/master/packages/primevue/src/slider/Slider.vue#L102-L103)) without transform-aware positioning +- This breaks interaction in transformed coordinate spaces where screen coordinates don't match logical element positions + +**Virtual Canvas Scroll Interference:** +- LiteGraph's infinite canvas uses scroll coordinates semantically for graph navigation via the `DragAndScale` coordinate system +- PrimeVue overlay components automatically trigger `scrollIntoView` behavior which interferes with this virtual positioning +- This issue is documented in [PrimeVue discussion #4270](https://github.com/orgs/primefaces/discussions/4270) where the feature request was made to disable this behavior + +**Historical Overlay Issues:** +- Previous z-index positioning conflicts required manual workarounds (commit `6d4eafb0`) where PrimeVue Dialog components needed `autoZIndex: false` and custom mask styling, later resolved by removing PrimeVue's automatic z-index management entirely + +**Minimal Update Overhead:** +- Analysis of git history shows only 2 PrimeVue version updates in 2+ years, indicating that upstream sync overhead is negligible for this project + +**Future Interaction System Requirements:** +- The ongoing canvas architecture evolution will require more granular control over component interaction and event handling as the transform-based system matures +- Predictable need for additional component modifications beyond current identified issues + +## Decision + +We will **NOT** fork PrimeVue. After evaluation, forking was determined to be unnecessarily complex and costly. + +**Rationale for Rejection:** + +- **Significant Implementation Complexity**: PrimeVue is structured as a monorepo ([primefaces/primevue](https://github.com/primefaces/primevue)) with significant code in a separate monorepo ([PrimeUIX](https://github.com/primefaces/primeuix)). Forking would require importing both repositories whole and selectively pruning or exempting components from our workspace tooling, adding substantial complexity. + +- **Alternative Solutions Available**: The modifications we identified (e.g., scroll interference issues, coordinate system conflicts) have less costly solutions that don't require maintaining a full fork. For example, coordinate issues could be addressed through event interception and synthetic event creation with scaled values. + +- **Maintenance Burden**: Ongoing maintenance and upgrades would be very painful, requiring manual conflict resolution and keeping pace with upstream changes across multiple repositories. + +- **Limited Tooling Support**: There isn't adequate tooling that provides the granularity needed to cleanly manage a PrimeVue fork within our existing infrastructure. + +## Consequences + +### Alternative Approach + +- **Use PrimeVue as External Dependency**: Continue using PrimeVue as a standard npm dependency +- **Targeted Workarounds**: Implement specific solutions for identified issues (coordinate system conflicts, scroll interference) without forking the entire library +- **Selective Component Replacement**: Use libraries like shadcn/ui to replace specific problematic PrimeVue components and adjust them to match our design system +- **Upstream Engagement**: Continue engaging with PrimeVue community for feature requests and bug reports +- **Maintain Flexibility**: Preserve ability to upgrade PrimeVue versions without fork maintenance overhead + +## Notes + +- Technical issues documented in the Context section remain valid concerns +- Solutions will be pursued through targeted fixes rather than wholesale forking +- Future re-evaluation possible if PrimeVue's architecture significantly changes or if alternative tooling becomes available +- This decision prioritizes maintainability and development velocity over maximum customization control diff --git a/docs/adr/README.md b/docs/adr/README.md index 00e50a639..5f6e5c2cf 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,6 +13,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 | +| [0004](0004-fork-primevue-ui-library.md) | Fork PrimeVue UI Library | Rejected | 2025-08-27 | ## Creating a New ADR diff --git a/eslint.config.js b/eslint.config.js index 7e3248b20..cddba3bbd 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -64,6 +64,9 @@ export default [ 'vue/no-v-html': 'off', // Enforce dark-theme: instead of dark: prefix 'vue/no-restricted-class': ['error', '/^dark:/'], + 'vue/multi-word-component-names': 'off', // TODO: fix + 'vue/no-template-shadow': 'off', // TODO: fix + 'vue/one-component-per-file': 'off', // TODO: fix // Restrict deprecated PrimeVue components 'no-restricted-imports': [ 'error', diff --git a/package.json b/package.json index 35b2b0ed7..c501ad230 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "tailwindcss": "^4.1.12", "tailwindcss-primeui": "^0.6.1", "tsx": "^4.15.6", + "tw-animate-css": "^1.3.8", "typescript": "^5.4.5", "typescript-eslint": "^8.42.0", "unplugin-icons": "^0.22.0", @@ -153,6 +154,7 @@ "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.2.5", + "reka-ui": "^2.5.0", "semver": "^7.7.2", "tailwind-merge": "^3.3.1", "three": "^0.170.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40bd4bd92..21c04dd0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: primevue: specifier: ^4.2.5 version: 4.2.5(vue@3.5.13(typescript@5.9.2)) + reka-ui: + specifier: ^2.5.0 + version: 2.5.0(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) semver: specifier: ^7.7.2 version: 7.7.2 @@ -342,6 +345,9 @@ importers: tsx: specifier: ^4.15.6 version: 4.19.4 + tw-animate-css: + specifier: ^1.3.8 + version: 1.3.8 typescript: specifier: ^5.4.5 version: 5.9.2 @@ -1622,6 +1628,18 @@ packages: '@firebase/webchannel-wrapper@1.0.3': resolution: {integrity: sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@floating-ui/vue@1.1.9': + resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==} + '@grpc/grpc-js@1.9.15': resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==} engines: {node: ^8.13.0 || >=10.10.0} @@ -1662,6 +1680,12 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@internationalized/date@3.9.0': + resolution: {integrity: sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==} + + '@internationalized/number@3.6.5': + resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} + '@intlify/core-base@9.14.3': resolution: {integrity: sha512-nbJ7pKTlXFnaXPblyfiH6awAx1C0PWNNuqXAR74yRwgi5A/Re/8/5fErLY0pv4R8+EHj3ZaThMHdnuC/5OBa6g==} engines: {node: '>= 16'} @@ -2295,6 +2319,9 @@ packages: storybook: ^9.1.1 vue: ^3.0.0 + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tailwindcss/node@4.1.12': resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} @@ -2385,6 +2412,14 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + + '@tanstack/vue-virtual@3.13.12': + resolution: {integrity: sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -2661,6 +2696,9 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} @@ -2926,12 +2964,21 @@ packages: '@vueuse/core@11.0.0': resolution: {integrity: sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==} + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + '@vueuse/metadata@11.0.0': resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==} + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + '@vueuse/shared@11.0.0': resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==} + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@webgpu/types@0.1.51': resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==} @@ -3093,6 +3140,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -3754,6 +3805,9 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -5427,6 +5481,9 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -5921,6 +5978,11 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + reka-ui@2.5.0: + resolution: {integrity: sha512-81aMAmJeVCy2k0E6x7n1kypDY6aM1ldLis5+zcdV1/JtoAlSDck5OBsyLRJU9CfgbrQp1ImnRnBSmC4fZ2fkZQ==} + peerDependencies: + vue: '>= 3.2.0' + relateurl@0.2.7: resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} engines: {node: '>= 0.10'} @@ -6374,6 +6436,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tw-animate-css@1.3.8: + resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -8405,6 +8470,26 @@ snapshots: '@firebase/webchannel-wrapper@1.0.3': {} + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@floating-ui/vue@1.1.9(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/utils': 0.2.10 + vue-demi: 0.14.10(vue@3.5.13(typescript@5.9.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.13 @@ -8456,6 +8541,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@internationalized/date@3.9.0': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.5': + dependencies: + '@swc/helpers': 0.5.17 + '@intlify/core-base@9.14.3': dependencies: '@intlify/message-compiler': 9.14.3 @@ -9240,6 +9333,10 @@ snapshots: vue: 3.5.13(typescript@5.9.2) vue-component-type-helpers: 3.0.6 + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.1.12': dependencies: '@jridgewell/remapping': 2.3.5 @@ -9311,6 +9408,13 @@ snapshots: tailwindcss: 4.1.12 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) + '@tanstack/virtual-core@3.13.12': {} + + '@tanstack/vue-virtual@3.13.12(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@tanstack/virtual-core': 3.13.12 + vue: 3.5.13(typescript@5.9.2) + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -9618,6 +9722,8 @@ snapshots: '@types/web-bluetooth@0.0.20': {} + '@types/web-bluetooth@0.0.21': {} + '@types/webxr@0.5.20': {} '@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)': @@ -10056,8 +10162,19 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/core@12.8.2(typescript@5.9.2)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - typescript + '@vueuse/metadata@11.0.0': {} + '@vueuse/metadata@12.8.2': {} + '@vueuse/shared@11.0.0(vue@3.5.13(typescript@5.9.2))': dependencies: vue-demi: 0.14.10(vue@3.5.13(typescript@5.9.2)) @@ -10065,6 +10182,12 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/shared@12.8.2(typescript@5.9.2)': + dependencies: + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - typescript + '@webgpu/types@0.1.51': {} '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': @@ -10217,6 +10340,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -11082,6 +11209,8 @@ snapshots: define-lazy-prop@3.0.0: {} + defu@6.1.4: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -13023,6 +13152,8 @@ snapshots: object-keys@1.1.1: {} + ohash@2.0.11: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -13643,6 +13774,23 @@ snapshots: dependencies: jsesc: 3.0.2 + reka-ui@2.5.0(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)): + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/vue': 1.1.9(vue@3.5.13(typescript@5.9.2)) + '@internationalized/date': 3.9.0 + '@internationalized/number': 3.6.5 + '@tanstack/vue-virtual': 3.13.12(vue@3.5.13(typescript@5.9.2)) + '@vueuse/core': 12.8.2(typescript@5.9.2) + '@vueuse/shared': 12.8.2(typescript@5.9.2) + aria-hidden: 1.2.6 + defu: 6.1.4 + ohash: 2.0.11 + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + relateurl@0.2.7: {} remark-frontmatter@5.0.0: @@ -14102,6 +14250,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tw-animate-css@1.3.8: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/scripts/cicd/extract-playwright-counts.ts b/scripts/cicd/extract-playwright-counts.ts new file mode 100755 index 000000000..ff6f44db3 --- /dev/null +++ b/scripts/cicd/extract-playwright-counts.ts @@ -0,0 +1,183 @@ +#!/usr/bin/env tsx +import fs from 'fs' +import path from 'path' + +interface TestStats { + expected?: number + unexpected?: number + flaky?: number + skipped?: number + finished?: number +} + +interface ReportData { + stats?: TestStats +} + +interface TestCounts { + passed: number + failed: number + flaky: number + skipped: number + total: number +} + +/** + * Extract test counts from Playwright HTML report + * @param reportDir - Path to the playwright-report directory + * @returns Test counts { passed, failed, flaky, skipped, total } + */ +function extractTestCounts(reportDir: string): TestCounts { + const counts: TestCounts = { + passed: 0, + failed: 0, + flaky: 0, + skipped: 0, + total: 0 + } + + try { + // First, try to find report.json which Playwright generates with JSON reporter + const jsonReportFile = path.join(reportDir, 'report.json') + if (fs.existsSync(jsonReportFile)) { + const reportJson: ReportData = JSON.parse( + fs.readFileSync(jsonReportFile, 'utf-8') + ) + if (reportJson.stats) { + const stats = reportJson.stats + counts.total = stats.expected || 0 + counts.passed = + (stats.expected || 0) - + (stats.unexpected || 0) - + (stats.flaky || 0) - + (stats.skipped || 0) + counts.failed = stats.unexpected || 0 + counts.flaky = stats.flaky || 0 + counts.skipped = stats.skipped || 0 + return counts + } + } + + // Try index.html - Playwright HTML report embeds data in a script tag + const indexFile = path.join(reportDir, 'index.html') + if (fs.existsSync(indexFile)) { + const content = fs.readFileSync(indexFile, 'utf-8') + + // Look for the embedded report data in various formats + // Format 1: window.playwrightReportBase64 + let dataMatch = content.match( + /window\.playwrightReportBase64\s*=\s*["']([^"']+)["']/ + ) + if (dataMatch) { + try { + const decodedData = Buffer.from(dataMatch[1], 'base64').toString( + 'utf-8' + ) + const reportData: ReportData = JSON.parse(decodedData) + + if (reportData.stats) { + const stats = reportData.stats + counts.total = stats.expected || 0 + counts.passed = + (stats.expected || 0) - + (stats.unexpected || 0) - + (stats.flaky || 0) - + (stats.skipped || 0) + counts.failed = stats.unexpected || 0 + counts.flaky = stats.flaky || 0 + counts.skipped = stats.skipped || 0 + return counts + } + } catch (e) { + // Continue to try other formats + } + } + + // Format 2: window.playwrightReport + dataMatch = content.match(/window\.playwrightReport\s*=\s*({[\s\S]*?});/) + if (dataMatch) { + try { + // Use Function constructor instead of eval for safety + const reportData = new Function( + 'return ' + dataMatch[1] + )() as ReportData + + if (reportData.stats) { + const stats = reportData.stats + counts.total = stats.expected || 0 + counts.passed = + (stats.expected || 0) - + (stats.unexpected || 0) - + (stats.flaky || 0) - + (stats.skipped || 0) + counts.failed = stats.unexpected || 0 + counts.flaky = stats.flaky || 0 + counts.skipped = stats.skipped || 0 + return counts + } + } catch (e) { + // Continue to try other formats + } + } + + // Format 3: Look for stats in the HTML content directly + // Playwright sometimes renders stats in the UI + const statsMatch = content.match( + /(\d+)\s+passed[^0-9]*(\d+)\s+failed[^0-9]*(\d+)\s+flaky[^0-9]*(\d+)\s+skipped/i + ) + if (statsMatch) { + counts.passed = parseInt(statsMatch[1]) || 0 + counts.failed = parseInt(statsMatch[2]) || 0 + counts.flaky = parseInt(statsMatch[3]) || 0 + counts.skipped = parseInt(statsMatch[4]) || 0 + counts.total = + counts.passed + counts.failed + counts.flaky + counts.skipped + return counts + } + + // Format 4: Try to extract from summary text patterns + const passedMatch = content.match(/(\d+)\s+(?:tests?|specs?)\s+passed/i) + const failedMatch = content.match(/(\d+)\s+(?:tests?|specs?)\s+failed/i) + const flakyMatch = content.match(/(\d+)\s+(?:tests?|specs?)\s+flaky/i) + const skippedMatch = content.match(/(\d+)\s+(?:tests?|specs?)\s+skipped/i) + const totalMatch = content.match( + /(\d+)\s+(?:tests?|specs?)\s+(?:total|ran)/i + ) + + if (passedMatch) counts.passed = parseInt(passedMatch[1]) || 0 + if (failedMatch) counts.failed = parseInt(failedMatch[1]) || 0 + if (flakyMatch) counts.flaky = parseInt(flakyMatch[1]) || 0 + if (skippedMatch) counts.skipped = parseInt(skippedMatch[1]) || 0 + if (totalMatch) { + counts.total = parseInt(totalMatch[1]) || 0 + } else if ( + counts.passed || + counts.failed || + counts.flaky || + counts.skipped + ) { + counts.total = + counts.passed + counts.failed + counts.flaky + counts.skipped + } + } + } catch (error) { + console.error(`Error reading report from ${reportDir}:`, error) + } + + return counts +} + +// Main execution +const reportDir = process.argv[2] + +if (!reportDir) { + console.error('Usage: extract-playwright-counts.ts ') + process.exit(1) +} + +const counts = extractTestCounts(reportDir) + +// Output as JSON for easy parsing in shell script +console.log(JSON.stringify(counts)) + +export { extractTestCounts } diff --git a/scripts/cicd/pr-playwright-deploy-and-comment.sh b/scripts/cicd/pr-playwright-deploy-and-comment.sh index 767a7f514..aeab37c8e 100755 --- a/scripts/cicd/pr-playwright-deploy-and-comment.sh +++ b/scripts/cicd/pr-playwright-deploy-and-comment.sh @@ -58,6 +58,12 @@ if ! command -v wrangler > /dev/null 2>&1; then } fi +# Check if tsx is available, install if not +if ! command -v tsx > /dev/null 2>&1; then + echo "Installing tsx..." >&2 + npm install -g tsx >&2 || echo "Failed to install tsx" >&2 +fi + # Deploy a single browser report, WARN: ensure inputs are sanitized before calling this function deploy_report() { dir="$1" @@ -159,12 +165,16 @@ else echo "Available reports:" ls -la reports/ 2>/dev/null || echo "Reports directory not found" - # Deploy all reports in parallel and collect URLs + # Deploy all reports in parallel and collect URLs + test counts temp_dir=$(mktemp -d) pids="" i=0 - # Start parallel deployments + # Store current working directory for absolute paths + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + BASE_DIR="$(pwd)" + + # Start parallel deployments and count extractions for browser in $BROWSERS; do if [ -d "reports/playwright-report-$browser" ]; then echo "Found report for $browser, deploying in parallel..." @@ -172,11 +182,26 @@ else url=$(deploy_report "reports/playwright-report-$browser" "$browser" "$cloudflare_branch") echo "$url" > "$temp_dir/$i.url" echo "Deployment result for $browser: $url" + + # Extract test counts using tsx (TypeScript executor) + EXTRACT_SCRIPT="$SCRIPT_DIR/extract-playwright-counts.ts" + REPORT_DIR="$BASE_DIR/reports/playwright-report-$browser" + + if command -v tsx > /dev/null 2>&1 && [ -f "$EXTRACT_SCRIPT" ]; then + echo "Extracting counts from $REPORT_DIR using $EXTRACT_SCRIPT" >&2 + counts=$(tsx "$EXTRACT_SCRIPT" "$REPORT_DIR" 2>&1 || echo '{}') + echo "Extracted counts for $browser: $counts" >&2 + echo "$counts" > "$temp_dir/$i.counts" + else + echo "Script not found or tsx not available: $EXTRACT_SCRIPT" >&2 + echo '{}' > "$temp_dir/$i.counts" + fi ) & pids="$pids $!" else echo "Report not found for $browser at reports/playwright-report-$browser" echo "failed" > "$temp_dir/$i.url" + echo '{}' > "$temp_dir/$i.counts" fi i=$((i + 1)) done @@ -186,8 +211,9 @@ else wait $pid done - # Collect URLs in order + # Collect URLs and counts in order urls="" + all_counts="" i=0 for browser in $BROWSERS; do if [ -f "$temp_dir/$i.url" ]; then @@ -200,37 +226,147 @@ else else urls="$urls $url" fi + + if [ -f "$temp_dir/$i.counts" ]; then + counts=$(cat "$temp_dir/$i.counts") + echo "Read counts for $browser from $temp_dir/$i.counts: $counts" >&2 + else + counts="{}" + echo "No counts file found for $browser at $temp_dir/$i.counts" >&2 + fi + if [ -z "$all_counts" ]; then + all_counts="$counts" + else + all_counts="$all_counts|$counts" + fi + i=$((i + 1)) done # Clean up temp directory rm -rf "$temp_dir" + # Calculate total test counts across all browsers + total_passed=0 + total_failed=0 + total_flaky=0 + total_skipped=0 + total_tests=0 + + # Parse counts and calculate totals + IFS='|' + set -- $all_counts + for counts_json; do + if [ "$counts_json" != "{}" ] && [ -n "$counts_json" ]; then + # Parse JSON counts using simple grep/sed if jq is not available + if command -v jq > /dev/null 2>&1; then + passed=$(echo "$counts_json" | jq -r '.passed // 0') + failed=$(echo "$counts_json" | jq -r '.failed // 0') + flaky=$(echo "$counts_json" | jq -r '.flaky // 0') + skipped=$(echo "$counts_json" | jq -r '.skipped // 0') + total=$(echo "$counts_json" | jq -r '.total // 0') + else + # Fallback parsing without jq + passed=$(echo "$counts_json" | sed -n 's/.*"passed":\([0-9]*\).*/\1/p') + failed=$(echo "$counts_json" | sed -n 's/.*"failed":\([0-9]*\).*/\1/p') + flaky=$(echo "$counts_json" | sed -n 's/.*"flaky":\([0-9]*\).*/\1/p') + skipped=$(echo "$counts_json" | sed -n 's/.*"skipped":\([0-9]*\).*/\1/p') + total=$(echo "$counts_json" | sed -n 's/.*"total":\([0-9]*\).*/\1/p') + fi + + total_passed=$((total_passed + ${passed:-0})) + total_failed=$((total_failed + ${failed:-0})) + total_flaky=$((total_flaky + ${flaky:-0})) + total_skipped=$((total_skipped + ${skipped:-0})) + total_tests=$((total_tests + ${total:-0})) + fi + done + unset IFS + + # Determine overall status + if [ $total_failed -gt 0 ]; then + status_icon="❌" + status_text="Some tests failed" + elif [ $total_flaky -gt 0 ]; then + status_icon="⚠️" + status_text="Tests passed with flaky tests" + elif [ $total_tests -gt 0 ]; then + status_icon="✅" + status_text="All tests passed!" + else + status_icon="🕵🏻" + status_text="No test results found" + fi + # Generate completion comment comment="$COMMENT_MARKER ## 🎭 Playwright Test Results -✅ **Tests completed successfully!** +$status_icon **$status_text** -⏰ Completed at: $(date -u '+%m/%d/%Y, %I:%M:%S %p') UTC +⏰ Completed at: $(date -u '+%m/%d/%Y, %I:%M:%S %p') UTC" + + # Add summary counts if we have test data + if [ $total_tests -gt 0 ]; then + comment="$comment + +### 📈 Summary +- **Total Tests:** $total_tests +- **Passed:** $total_passed ✅ +- **Failed:** $total_failed $([ $total_failed -gt 0 ] && echo '❌' || echo '') +- **Flaky:** $total_flaky $([ $total_flaky -gt 0 ] && echo '⚠️' || echo '') +- **Skipped:** $total_skipped $([ $total_skipped -gt 0 ] && echo '⏭️' || echo '')" + fi + + comment="$comment ### 📊 Test Reports by Browser" - # Add browser results + # Add browser results with individual counts i=0 - for browser in $BROWSERS; do + IFS='|' + set -- $all_counts + for counts_json; do + # Get browser name + browser=$(echo "$BROWSERS" | cut -d' ' -f$((i + 1))) # Get URL at position i url=$(echo "$urls" | cut -d' ' -f$((i + 1))) if [ "$url" != "failed" ] && [ -n "$url" ]; then + # Parse individual browser counts + if [ "$counts_json" != "{}" ] && [ -n "$counts_json" ]; then + if command -v jq > /dev/null 2>&1; then + b_passed=$(echo "$counts_json" | jq -r '.passed // 0') + b_failed=$(echo "$counts_json" | jq -r '.failed // 0') + b_flaky=$(echo "$counts_json" | jq -r '.flaky // 0') + b_skipped=$(echo "$counts_json" | jq -r '.skipped // 0') + b_total=$(echo "$counts_json" | jq -r '.total // 0') + else + b_passed=$(echo "$counts_json" | sed -n 's/.*"passed":\([0-9]*\).*/\1/p') + b_failed=$(echo "$counts_json" | sed -n 's/.*"failed":\([0-9]*\).*/\1/p') + b_flaky=$(echo "$counts_json" | sed -n 's/.*"flaky":\([0-9]*\).*/\1/p') + b_skipped=$(echo "$counts_json" | sed -n 's/.*"skipped":\([0-9]*\).*/\1/p') + b_total=$(echo "$counts_json" | sed -n 's/.*"total":\([0-9]*\).*/\1/p') + fi + + if [ -n "$b_total" ] && [ "$b_total" != "0" ]; then + counts_str=" • ✅ $b_passed / ❌ $b_failed / ⚠️ $b_flaky / ⏭️ $b_skipped" + else + counts_str="" + fi + else + counts_str="" + fi + comment="$comment -- ✅ **${browser}**: [View Report](${url})" +- ✅ **${browser}**: [View Report](${url})${counts_str}" else comment="$comment - ❌ **${browser}**: Deployment failed" fi i=$((i + 1)) done + unset IFS comment="$comment diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 3258cbba5..9c49e1704 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -2,8 +2,9 @@ @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/utilities' layer(utilities); +@import 'tw-animate-css'; -@plugin "tailwindcss-primeui"; +@plugin 'tailwindcss-primeui'; @config '../../../tailwind.config.ts'; @@ -114,6 +115,14 @@ --color-dark-elevation-2: rgba(from white r g b / 0.03); } +@theme inline { + --color-node-component-surface: var(--color-charcoal-300); + --color-node-component-surface-highlight: var(--color-slate-100); + --color-node-component-surface-hovered: var(--color-charcoal-500); + --color-node-component-surface-selected: var(--color-charcoal-700); + --color-node-stroke: var(--color-stone-100); +} + @custom-variant dark-theme { .dark-theme & { @slot; diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index 24b3f8858..44bd579a6 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -37,7 +37,7 @@ const visible = computed(() => position.value !== 'Disabled') const topMenuRef = inject>('topMenuRef') const panelRef = ref(null) const dragHandleRef = ref(null) -const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', false) +const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', true) const storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', { x: 0, y: 0 diff --git a/src/components/dialog/content/ApiNodesSignInContent.vue b/src/components/dialog/content/ApiNodesSignInContent.vue index f4765a809..af6f4fff6 100644 --- a/src/components/dialog/content/ApiNodesSignInContent.vue +++ b/src/components/dialog/content/ApiNodesSignInContent.vue @@ -1,4 +1,3 @@ - - - diff --git a/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue b/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue index 5d7e46f5d..268a7c097 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue @@ -19,7 +19,7 @@ defineProps<{ {{ widget.name }}