Compare commits

...

77 Commits

Author SHA1 Message Date
Comfy Org PR Bot
5b866b74b8 1.28.3 (#5822)
Patch version increment to 1.28.3

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5822-1-28-3-27b6d73d365081f18315d03ad55987aa)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
2025-09-27 17:05:43 -05:00
Christian Byrne
e8b9f8f4bc enforce test file-naming rule (#5820)
## Summary

Enforced test file naming conventions with ESLint rules and renamed 26
test files from `.spec.ts` to `.test.ts`.

## Changes

- **What**: Added ESLint rules to enforce `.spec.ts` files only in
`browser_tests/tests/` and `.test.ts` files only in `src/`
- **What**: Renamed 26 component/unit test files from `.spec.ts` to
`.test.ts` to comply with new convention

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5820-enforce-test-file-naming-rule-27b6d73d365081269b32ddcc9d3a5048)
by [Unito](https://www.unito.io)
2025-09-27 13:53:25 -07:00
Christian Byrne
b0d2b44dad [test] add browser tests for Vue node multiline string widgets (#5819)
## Summary

Added Playwright E2E tests for Vue multiline string widget functionality
to ensure text input and persistence work correctly.

## Changes

- **What**: Created browser tests for multiline string widget in Vue
nodes implementation, covering text input, multiline text input, and
focus change behavior.

## Review Focus

Test coverage for text input persistence across focus changes and
multiline content handling in the CLIP Text Encode widget.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5819-test-add-browser-tests-for-Vue-node-multiline-string-widgets-27b6d73d365081e2916ae663e2d44899)
by [Unito](https://www.unito.io)
2025-09-27 12:45:24 -07:00
Christian Byrne
976b1a6bbd [test] add browser test to verify Vue node body double-click behavior (#5818)
## Summary

Added test coverage to verify double-clicking node body does not trigger
title edit mode, mirroring test in Litegraph
856eb446a5/browser_tests/tests/interaction.spec.ts (L361)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5818-test-add-browser-test-to-verify-Vue-node-body-double-click-behavior-27b6d73d3650812f9efef1459d5d465f)
by [Unito](https://www.unito.io)
2025-09-27 12:07:33 -07:00
Christian Byrne
856eb446a5 Add node pinning functionality to Vue nodes (#5772)
## Summary

Added pinning functionality to Vue nodes with hotkey support and visual
indicators.

## Changes

- **What**: Added node pinning feature with 'p' hotkey toggle and pin
icon indicator
- **Components**: Updated `LGraphNode.vue` and `NodeHeader.vue` with pin
state tracking
- **State Management**: Extended `useGraphNodeManager` to sync pinned
flag with Vue components
- **Tests**: Added E2E tests for single and multi-node pin toggling

## Review Focus

Pin state persistence in graph serialization and visual indicator
positioning in node header layout. Verify hotkey doesn't conflict with
existing shortcuts.

## Technical Details

- Pin state tracked via `flags.pinned` property in `LGraphNode`
- Uses [Vue
memoization](https://vuejs.org/api/reactivity-advanced.html#v-memo) for
efficient header re-rendering
- Integrates with existing node property change detection system
- Visual indicator uses Lucide pin icon with theme-aware styling

## Screenshots (if applicable)

<img width="875" height="977" alt="Screenshot from 2025-09-25 13-02-21"
src="https://github.com/user-attachments/assets/51d46cea-08f0-44fb-8b07-56d1b939338f"
/>

<img width="875" height="977" alt="Screenshot from 2025-09-25 13-02-10"
src="https://github.com/user-attachments/assets/ce247426-1e39-48c0-924b-658b65c24f52"
/>

## Related 

- https://github.com/Comfy-Org/ComfyUI_frontend/pull/5715

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5772-Add-node-pinning-functionality-to-Vue-nodes-2796d73d36508195914bcfc986aa66b5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-27 10:56:46 -07:00
Simula_r
992ba9d585 style: LOD color theme (#5812)
## Summary

light mode styles for lod system

## Screenshots (if applicable)

<img width="1353" height="751" alt="image"
src="https://github.com/user-attachments/assets/3caf2e7d-10c3-4ecb-bcd6-b605a643065b"
/>

<img width="1320" height="705" alt="image"
src="https://github.com/user-attachments/assets/cb4a2675-f644-4564-af57-aa9f3920fb05"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5812-style-LOD-color-theme-27b6d73d365081fe8cd2c8c5a52b2493)
by [Unito](https://www.unito.io)
2025-09-27 10:52:28 -07:00
Christian Byrne
4b9d448b6c fix style of Select COMBO Vue node widget (#5785)
## Summary

Fixed `WidgetSelect` component styling to match other Vue node widgets
by using shared base styles.

## Changes

- **What**: Replaced hardcoded color classes with `WidgetInputBaseClass`
for consistent styling across Vue node widgets

## Review Focus

Visual consistency with other input widgets in Vue nodes interface and
proper styling inheritance.

## Screenshots (if applicable)

*Before*:

<img width="1645" height="1436" alt="Screenshot from 2025-09-25
18-12-16"
src="https://github.com/user-attachments/assets/0b985616-0cf5-44e3-8459-739d194dec7a"
/>

*After*:

<img width="1645" height="1436" alt="Screenshot from 2025-09-25
18-12-07"
src="https://github.com/user-attachments/assets/235437cf-3e1f-4bc0-8822-26fb8606b437"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5785-fix-style-of-Select-COMBO-Vue-node-widget-27a6d73d3650811a959ef8a955274987)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-27 10:28:26 -07:00
Christian Byrne
0ac1074a25 fix Vue nodes capturing wheel event when Ctrl+Shift+drag zooming (#5791)
## Summary

Fixed canvas read-only state during Ctrl+Shift drag-to-zoom to prevent
node interaction conflicts. This fix also benefits Litegraph nodes.

## Changes

- **What**: Canvas becomes read-only during [Ctrl+Shift drag
zoom](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent)
operations and restores original state when finished
- **What**: Added browser test coverage for zoom interaction behavior

## Review Focus

State restoration on zoom interruption and edge cases where drag zoom is
cancelled mid-operation.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5791-fix-Vue-nodes-capturing-wheel-event-when-Ctrl-Shift-drag-zooming-27a6d73d3650817d888ee59f8f7fcb01)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-27 10:25:12 -07:00
Arjan Singh
45f5ec15c2 [fix] remove unnecessary afterEach (#5817)
## Summary

We didn't need the `afterEach` in this test.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5817-fix-remove-unnecessary-afterEach-27b6d73d3650814bb4f1c426c5b17a54)
by [Unito](https://www.unito.io)
2025-09-27 09:36:41 -07:00
Christian Byrne
a519e681dd Use workspace in knip config (#5809)
## Summary

After the `pnpm`/`Nx` monorepo migration, `knip` emitted configuration
hints like “Remove or move unused top-level entry…/project…” because it
discovered multiple workspaces but our config still declared global
`entry`/`project` patterns:

```
  > knip --cache

  Configuration hints (4)
  Remove or move unused top-level entry to one of workspaces: [{build,scripts}/**/*.{js,ts}, …]
  Remove or move unused top-level project to one of workspaces: [**/*.{js,ts,vue}, …]
  Remove from ignoreBinaries: only-allow
  Remove from ignoreBinaries: openapi-typescript
```

Rewriting `knip.config.ts` to define those patterns inside the
`workspaces` map for `'.'`, `packages/tailwind-utils`, and
`packages/design-system` matches Knip’s documented workspace schema
(root key `'.'` plus per-package overrides) and aligns each package’s
source with the files it actually exposes.

Follow-up: trim `packages/design-system` entries to the real public
surface, add a wildcard workspace default for future packages, and
capture this expectation somewhere in the repo's docs.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5809-Use-workspace-in-knip-config-27b6d73d365081b8ace9c82aee78b1db)
by [Unito](https://www.unito.io)
2025-09-27 09:35:01 -07:00
Arjan Singh
fdb15e1296 [fix] remove unnecessary awaits (#5806)
## Summary

Cleans up
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts

1. Removes unnecessary ticks.
2. Cleans up `beforeEach`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5806-fix-remove-unnecessary-awaits-27a6d73d36508103ae1ccb8199195905)
by [Unito](https://www.unito.io)
2025-09-27 09:17:23 -07:00
Alexander Brown
d444b2c285 Test: Double timeout for Version Mismatch Warnings (#5814)
## Summary

`should persist dismissed state across sessions`


https://ccfbaff5.comfyui-playwright-chromium.pages.dev/#?testId=b07187838e41a32597d7-951ff47f196f7f22f516


https://7bf44be0.comfyui-playwright-chromium.pages.dev/#?testId=b07187838e41a32597d7-951ff47f196f7f22f516

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5814-Test-Double-timeout-for-Version-Mismatch-Warnings-27b6d73d365081be8c73c9a12759f83e)
by [Unito](https://www.unito.io)
2025-09-27 20:08:00 +10:00
Christian Byrne
46ad1318e5 Implement fit-to-view for Vue nodes (#5782)
## Summary

Implemented fit-to-view functionality for Vue nodes with bounds
calculation and viewport animation support.


https://github.com/user-attachments/assets/2ec221f1-9194-4564-95f9-ad4da80f190a

## Changes

- **What**: Added Vue nodes support to fit-to-view command with [bounds
calculation](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)
and LiteGraph integration
- **Dependencies**: Added dependency on `layoutStore` and
`selectionBounds` utility

## Review Focus

Bounds calculation accuracy for complex node layouts and animation
performance with large node selections. Verify proper fallback to legacy
LiteGraph behavior when Vue nodes disabled.

```mermaid
graph TD
    A[Fit to View Command] --> B{Vue Nodes Enabled?}
    B -->|Yes| C[Get Selected Nodes]
    B -->|No| D[Legacy LiteGraph Method]
    C --> E{Nodes Selected?}
    E -->|Yes| F[Calculate Selected Bounds]
    E -->|No| G[Calculate All Nodes Bounds]
    F --> H[Convert to LiteGraph Format]
    G --> H
    H --> I[Animate to Bounds]
    D --> J[Canvas fitViewToSelectionAnimated]
    
    style A fill:#f9f9f9,stroke:#333,color:#333
    style I fill:#f9f9f9,stroke:#333,color:#333
    style J fill:#f9f9f9,stroke:#333,color:#333
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5782-Implement-fit-to-view-for-Vue-nodes-27a6d73d365081cb822cd93f557e77b2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-26 21:32:31 -07:00
Johnpaul Chiwetelu
da332ed75d Add compact menu style for smaller window heights (#5323)
This pull request enhances the responsiveness of the `CommandMenubar`
component by introducing a "compact" mode for screens with limited
vertical space. When the window height is less than 700px, the menubar
adapts its submenu positioning and styling to improve usability on
smaller displays. The most important changes are grouped below:

**Responsive Behavior and State Management:**

* Added a reactive `windowHeight` reference and a computed
`isCompactHeight` property to detect when the window height is below
700px, enabling "compact" mode for the menubar. Event listeners for
window resize are now managed using `onMounted` and `onUnmounted`
lifecycle hooks to keep the state updated.
[[1]](diffhunk://#diff-10ee0e60e600a168bdd45e0b9f05a148f5b9ee48f54e6c7dee737ebe7b6192e6R314-R326)
[[2]](diffhunk://#diff-10ee0e60e600a168bdd45e0b9f05a148f5b9ee48f54e6c7dee737ebe7b6192e6L103-R104)

**Dynamic Styling and Class Application:**

* Updated the root element's class bindings to apply the
`comfy-command-menu-compact` class when `isCompactHeight` is true,
enabling conditional styling for compact mode.

**Compact Mode Styling:**

* Introduced new CSS rules for `.comfy-command-menu-compact` to force
submenus to open to the right and align with the top of the menu
container, ensuring better usability on short screens. Adjustments
include submenu positioning, alignment for nested menus, and ensuring
the submenu container uses the full available height.
* 
/fix #5289




https://github.com/user-attachments/assets/e7908b57-3f07-4fc4-a119-66f2b7e398c5





┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5323-Add-compact-menu-style-for-smaller-window-heights-2636d73d3650816cb5aaf4ac76c39739)
by [Unito](https://www.unito.io)
2025-09-26 19:13:50 -07:00
Christian Byrne
75d31f9d57 fix large integer precision handling in Vue int widgets (#5787)
## Summary

Fixed increment/decrement button lockup in number widgets when values
exceed JavaScript's safe integer limit (2^53 - 1).

## Changes

- **What**: Added precision-aware button disabling and user feedback to
`WidgetInputNumberInput` component using
[Number.isSafeInteger()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger)
- We still need to support values greater than 2^53 because they may be
in workflows.

## Review Focus

JavaScript floating-point precision behavior at scale - buttons hide
when arithmetic operations like `value + 1` would be unreliable due to
IEEE 754 limitations. Test coverage includes edge cases (NaN, Infinity)
and boundary conditions at MAX_SAFE_INTEGER.

```mermaid
graph TD
    A[User Input] --> B{Value > 2^53?}
    B -->|No| C[Show Buttons]
    B -->|Yes| D[Hide Buttons]
    D --> E[Show Tooltip]
    E --> F[User Can Still Type]
    
    style A fill:#f9f9f9,stroke:#333,color:#000
    style F fill:#f9f9f9,stroke:#333,color:#000
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5787-fix-large-integer-precision-handling-in-Vue-int-widgets-27a6d73d365081d9ae00e485740cfafb)
by [Unito](https://www.unito.io)
2025-09-26 18:11:41 -07:00
Christian Byrne
edcbcdfa84 fix flaky templates browser test (#5808)
Fixes flaky templates modal test introduced in
https://github.com/Comfy-Org/ComfyUI_frontend/pull/5802 by ensuring the
templates modal is visible before querying the visible card count and
asserting that it is greater than 0.

If we immediately count the number of cards after executing the "load
templates" command, it's possible that there are 0 visible due to them
loading (rather than being caused by a legitimate bug).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5808-fix-flaky-templates-browser-test-27b6d73d365081e58c65f608944976a0)
by [Unito](https://www.unito.io)
2025-09-26 18:01:34 -07:00
Christian Byrne
1f4a52ca3e Make searchbox first focused element in settings dialog (#5804)
## Summary

Resolves https://github.com/Comfy-Org/ComfyUI_frontend/issues/5164 by
adding `autofocus` prop to common `SearchBox` component then enabled for
the settings dialog search box (changing first focused element from the
close button). Previously, searchbox was the 2nd item in tab order.

## Review Focus

Accessibility implications of automatic focus and potential interference
with screen readers or keyboard navigation patterns. Already discussed
this previously w.r.t. accessibility, but only now implementing.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5804-Make-searchbox-first-focused-element-in-settings-dialog-27a6d73d3650814997facfa2418e1d40)
by [Unito](https://www.unito.io)
2025-09-26 13:28:30 -07:00
Johnpaul Chiwetelu
f2e925de62 Right click vue nodes (#5790)
This pull request refactors and improves the "More Options" popover
functionality for graph nodes in the UI. The main change is a rename and
redesign of the menu component from `MoreOptions` to `NodeOptions`,
introducing a global singleton pattern for popover control and enabling
context menu support on node right-click. This results in better
maintainability, more flexible triggering, and improved user experience.

**Node Options popover refactor and global control:**

* Renamed and refactored `MoreOptions.vue` to `NodeOptions.vue`,
removing the embedded button and exposing imperative methods (`toggle`,
`hide`, `isOpen`) for external control. The component now
registers/unregisters itself globally via `registerNodeOptionsInstance`.
[[1]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL2-R2)
[[2]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL203-R197)
[[3]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eR294-R309)
* Added `NodeOptionsButton.vue` as a dedicated button component for
triggering the popover, decoupling the button UI from the popover logic.
* Implemented a global singleton pattern in `useMoreOptionsMenu.ts` for
controlling the `NodeOptions` popover from anywhere, with
`toggleNodeOptions` and `registerNodeOptionsInstance` functions.
[[1]](diffhunk://#diff-ae87bdb1e06725eb19b8d0fc82ec40a5f8ca831a6632767cc5d214fc903b89b6R35-R65)
[[2]](diffhunk://#diff-ae87bdb1e06725eb19b8d0fc82ec40a5f8ca831a6632767cc5d214fc903b89b6L184-R216)

**UI integration and event handling improvements:**

* Updated `SelectionToolbox.vue` to use the new `NodeOptionsButton`
instead of the previous embedded `MoreOptions` button, and added the
`NodeOptions` popover to the main `GraphCanvas.vue` template for global
accessibility.
[[1]](diffhunk://#diff-05d80ee1e28e634dc758394ddf1bfaa8e5ec72a186a6ea2e2b6f5dfba867b264L41-R41)
[[2]](diffhunk://#diff-05d80ee1e28e634dc758394ddf1bfaa8e5ec72a186a6ea2e2b6f5dfba867b264L71-R71)
[[3]](diffhunk://#diff-aaf17c713f29c6db8ea03efe7fc3483a858982e818a324b23cff89859e71559cR65)
[[4]](diffhunk://#diff-aaf17c713f29c6db8ea03efe7fc3483a858982e818a324b23cff89859e71559cR91)
* Added right-click context menu support to `LGraphNode.vue`, triggering
the node options popover at the cursor position and integrating with
node selection logic.
[[1]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R45)
[[2]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R141)
[[3]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2L180-R187)
[[4]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R249-R263)

**Minor improvements and cleanup:**

* Updated references and variable names throughout the codebase to
reflect the new `NodeOptions` naming and logic.
[[1]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL53)
[[2]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eR50)
[[3]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL75-R60)
[[4]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL91-L95)
[[5]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL110-R90)
[[6]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL133-R113)
[[7]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL146-R126)
[[8]](diffhunk://#diff-e0dbd5e37efd2c79e7317415455340b0dd150b758077b170a663f67d2453605eL157-R140)

This refactor makes the node options menu more modular, easier to
maintain, and more flexible for future UI improvements.



https://github.com/user-attachments/assets/9c2f2556-4544-4e20-9f22-8f485b0ceadc

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5790-Right-click-vue-nodes-27a6d73d365081a98263c88d2e09e629)
by [Unito](https://www.unito.io)
2025-09-26 13:22:06 -07:00
Christian Byrne
5f7c7ca949 [ci] Update browser tests for new Templates modal (#5802)
## Summary
- Regenerate 3 Playwright screenshot baselines to reflect UI changes
from https://github.com/Comfy-Org/ComfyUI_frontend/pull/5142
- Also fixed [this
case](https://f01efc75.comfyui-playwright-chromium.pages.dev/#?testId=35f0453d615a452757ca-379124415c5b7e9060d2)
(test case for responsive sizing) as it was using outdated logic. New
logic: ensures that the nav is collapsed on mobile but visible on tablet
and desktop screen sizes. It also ensures that, at all the main
breakpoints, at least 1 card is visible (covers bug that the case was
originally written for wherein the nav was fully covering the cards at
narrow screen widths).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5802-ci-Regenerate-Playwright-screenshot-baselines-27a6d73d365081768211da0d24bad2c3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-26 13:10:41 -07:00
Arjan Singh
62441fa0f9 [fix] do not drag on right-click + fix refs (#5784)
## Summary

Fixes drag handling logic.

## Changes

Only check for drag on left-click.

Adds handler logic for following pointer events:
1. drag termination
2. context menu
3. pointer cancel

Adds tests.

Consolidates cleanup tasks.

## Screenshots

Fixed State:

Ignore first failed drag, browser window didn't have context.


https://github.com/user-attachments/assets/00ec685a-1ef7-4102-b19b-4cdb9b201d22

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5784-fix-do-not-drag-on-right-click-fix-refs-27a6d73d3650812ea797fccf14022568)
by [Unito](https://www.unito.io)
2025-09-26 12:05:36 -07:00
Rizumu Ayaka
c96f719f91 feat: dropdown widgets vue node ui (#5624)
- Load media dropdown widgets
- Load models dropdown widgets

I added a lot of feedback effects during interactions.
I tried my best to break the Dropdown into small components.
To make it more flexible, I provided many configurable props and
v-model.

<img width="1000" alt="CleanShot 2025-09-18 at 01 54 38"
src="https://github.com/user-attachments/assets/1a413078-1547-44b8-8b48-1ce8f8e764b5"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5624-feat-dropdown-widgets-vue-node-ui-2716d73d36508115a52bc1fb6d6376d0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2025-09-26 12:04:39 -07:00
filtered
9f19d8fb4b Migrate Tailwind styles to design-system package (#5794)
## Summary

Consolidates Tailwind configuration and styles into a shared
`@comfyorg/design-system` package for reuse across monorepo apps.

The goal was not to make changes to how the design system works; merely
to separate it cleanly. I _would_ strongly recommend some drastic
sweeping changes, however I believe that should be done after the
migration.

## Changes

- **What**: Migrates CSS files, Tailwind config, and custom icons to
design-system package
- **Dependencies**: Moves `@iconify-json/lucide` and `@iconify/tailwind`
to design-system package
2025-09-26 12:01:53 -07:00
Johnpaul Chiwetelu
d954336973 New Workflow Templates Modal (#5142)
This pull request refactors and simplifies the template workflow card
components and related UI in the codebase. The main changes focus on
removing unused or redundant components, improving visual and
interaction consistency, and enhancing error handling for images. Below
are the most important changes grouped by theme:

**Template Workflow Card Refactor and Cleanup**

* Removed the `TemplateWorkflowCard.vue` component and its associated
test file `TemplateWorkflowCard.spec.ts`, as well as the
`TemplateWorkflowCardSkeleton.vue` and `TemplateWorkflowList.vue`
components, indicating a shift away from the previous card-based
template workflow UI.
[[1]](diffhunk://#diff-49569af0404058e8257f3cc0716b066517ce7397dd58744b02aa0d0c61f2a815L1-L139)
[[2]](diffhunk://#diff-9fa6fc1470371f0b520d4deda4129fb313b1bea69888a376556f4bd824f9d751L1-L263)
[[3]](diffhunk://#diff-bc35b6f77d1cee6e86b05d0da80b7bd40013c7a6a97a89706d3bc52573e1c574L1-L30)
[[4]](diffhunk://#diff-48171f792b22022526fca411d3c3a366d48b675dab77943a20846ae079cbaf3bL1-L68)
* Removed the `TemplateSearchBar.vue` component, suggesting a redesign
or replacement of the search/filter UI for templates.

**UI and Interaction Improvements**

* Improved the `CardBottom.vue` component by making its height
configurable via a `fullHeight` prop, enhancing layout flexibility.
* Updated the `CardContainer.vue` component to add hover effects
(background, border, shadow, and padding) and support a new `none`
aspect ratio for more flexible card layouts.

**Image and Input Enhancements**

* Enhanced the `LazyImage.vue` component to display a default
placeholder image when an image fails to load, improving error handling
and user experience.
* Improved the `SearchBox.vue` component by making the input focusable
when clicking anywhere on the wrapper, and added a template ref for
better accessibility and usability.
[[1]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bL2-R5)
[[2]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bL16-R17)
[[3]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bR33-R39)

**Minor UI Tweaks**

* Adjusted label styling in `SingleSelect.vue` to remove unnecessary
overflow handling, simplifying the visual layout.

---------

Co-authored-by: Benjamin Lu <benceruleanlu@proton.me>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: snomiao <snomiao@gmail.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
2025-09-26 11:52:19 -07:00
snomiao
49f373c46f [chore] Replace npx with pnpx across the codebase (#5329)
## Summary
- Migrated all `npx` commands to `pnpx` to align with the pnpm ecosystem
- Updated all occurrences across the codebase for consistency

## Changes
- **Package.json scripts**: Updated test:browser, preinstall, and
collect-i18n scripts
- **GitHub Actions workflows**: Updated all workflow files that use npx
(test-ui, i18n, update-manager-types, etc.)
- **Documentation**: Updated browser_tests/README.md and
docs/extensions/development.md
- **Husky pre-commit hook**: Updated lint-staged and tsx commands
- **MCP configuration**: Updated .mcp.json to use pnpx

## Test Plan
- [ ] CI tests pass
- [ ] Local development commands work with pnpx
- [ ] GitHub Actions workflows execute successfully

🤖 Generated with Claude Code

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5329-chore-Replace-npx-with-pnpx-across-the-codebase-2646d73d36508127bd43d14b8364aeb1)
by [Unito](https://www.unito.io)
2025-09-26 10:06:41 -07:00
AustinMroz
9678a87846 Subgraph widget promotion - Part 2 (#5617)
Implements proxyWidget support on subgraph nodes.  This registers a
special proxyWidgets property on subgraph nodes which is directly mapped
to the proxyWidgets displayed on the node. Each proxyWidget directly
maps to a real widget inside the subgraph.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5617-Subgraph-widget-promotion-Part-2-2716d73d3650813d8621fefdce6ae518)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-09-26 10:24:55 -05:00
snomiao
3a9365af13 fix(collect-i18n-node-defs): refactor to run ComfyNodeDefImpl only in browser context (#5775)
## The Problem

The `collect-i18n-node-defs.ts` script started failing ~3 weeks ago when
Vue nodes were introduced ([commit
006e6bd57](https://github.com/Comfy-Org/ComfyUI_frontend/commit/006e6bd57),
[PR #4263](https://github.com/Comfy-Org/ComfyUI_frontend/pull/4263)).
The issue stems from:

1. **Import chain bringing Vue components into Node.js context:**
   ```
   collect-i18n-node-defs.ts
     ↓ imports
   ComfyNodeDefImpl (from nodeDefStore.ts)
     ↓ imports
   useSubgraphStore (from subgraphStore.ts)
     ↓ transitively imports
   executionStore.ts
     ↓ imports
   ChatHistoryWidget.vue (Vue component!)
   ```

2. **TypeScript `declare` fields causing Babel errors:**
   ```
TypeScript 'declare' fields must first be transformed by
@babel/plugin-transform-typescript
   ```

## This Solution vs PR #5515

### PR #5515 Approach (Complex)
- Adds custom Babel plugins and configurations
- Implements automatic browser globals injection
- Requires **47,517 additions, 9,469 deletions**
- Modifies the entire Playwright babel transformation pipeline

### This PR's Approach (Simple)
- Uses dynamic imports to defer module loading until runtime
- Avoids Babel compilation of problematic TypeScript/Vue files
- **Only 40 lines changed** in a single file
- No configuration changes needed

## How This Fix Works

```typescript
// Instead of static import that Babel tries to compile:
// import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'

// We use:
// 1. Type-only import (erased at runtime)
import type { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'

// 2. Dynamic import at runtime (bypasses Babel)
const { ComfyNodeDefImpl: ComfyNodeDefImplClass } = await import(
  '../src/stores/nodeDefStore'
)
```

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-25 22:56:17 -07:00
Christian Byrne
3ee0d394ca fix flaky version mismatch warning browser test (#5792)
Adds a wait to ensure the dismissed state is saved to localstorage
properly before `setup` (page reload).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5792-fix-broken-test-27a6d73d365081e69c1febbc246448fa)
by [Unito](https://www.unito.io)
2025-09-25 21:15:03 -07:00
Johnpaul Chiwetelu
6a70152220 Wheel Selection Toolbox and Popover (#5781)
This pull request updates event handling in the selection toolbox
components to ensure that mouse wheel events are properly forwarded to
the canvas, improving interaction consistency across the UI.

**Event handling improvements:**

* Changed the wheel event handler in `SelectionToolbox.vue` to use
`canvasInteractions.forwardEventToCanvas` instead of
`canvasInteractions.handleWheel`, ensuring wheel events are consistently
forwarded to the canvas.
* Added the wheel event handler to the `MoreOptions.vue` popover,
forwarding wheel events to the canvas for submenu interactions.

**Code organization updates:**

* Imported `useCanvasInteractions` in `MoreOptions.vue` to access the
new event forwarding method.
* Instantiated `canvasInteractions` in `MoreOptions.vue` for use in
event handling.## Summary


https://github.com/user-attachments/assets/46046086-35e8-4cd1-a11a-365705beb9cd

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5781-Wheel-Selection-Toolbox-and-Popover-27a6d73d3650812c9c4fe8440ff7dd1d)
by [Unito](https://www.unito.io)
2025-09-25 20:51:24 -07:00
Jin Yi
5c1e00ff8e Refactor conflict detection system and move to manager extension (#5436)
## Refactor conflict detection system and move to manager extension

### Description

This PR refactors the conflict detection system, moving it from the
global composables to the manager extension folder for better code
organization. Additionally, it improves test type safety and adds
comprehensive test coverage for utility functions.

### Main Changes

#### 📦 Code Organization
- **Moved conflict detection to manager extension** - Relocated all
conflict detection related composables, stores, and utilities from
global scope to `/workbench/extensions/manager/` for better modularity
(https://github.com/Comfy-Org/ComfyUI_frontend/pull/5722)
- **Moved from** `src/composables/useConflictDetection.ts` **to**
`src/workbench/extensions/manager/composables/useConflictDetection.ts`
- Moved related stores and composables to maintain cohesive module
structure

#### ♻️ Refactoring
- **Extracted utility functions** - Split conflict detection logic into
separate utility modules:
  - `conflictUtils.ts` - Conflict consolidation and summary generation
- `systemCompatibility.ts` - OS and accelerator compatibility checking
  - `versionUtil.ts` - Version compatibility checking
- **Removed duplicate state management** - Cleaned up redundant state
and unused functions
- **Improved naming conventions** - Renamed functions for better clarity
- **Removed unused system environment code** - Cleaned up deprecated
code

#### 🔧 Test Improvements
- **Fixed TypeScript errors** in all test files - removed all `any` type
usage
- **Added comprehensive test coverage**:
  - `conflictUtils.test.ts` - 299 lines of tests for conflict utilities
- `systemCompatibility.test.ts` - 270 lines of tests for compatibility
checking
  - `versionUtil.test.ts` - 342 lines of tests for version utilities
- **Updated mock objects** to match actual implementations
- **Aligned with backend changes** - Updated SystemStats structure to
include `pytorch_version`, `embedded_python`,
`required_frontend_version`

#### 🐛 Bug Fixes
- **Fixed OS detection bug** - Resolved issue where 'darwin' was
incorrectly matched as 'Windows' due to containing 'win' substring
- **Fixed import paths** - Updated all import paths after moving to
manager extension
- **Fixed unused exports** - Removed all unused function exports
- **Fixed lint errors** - Resolved all ESLint and Prettier issues

### File Structure Changes

```
Before:
src/
├── composables/
│   └── useConflictDetection.ts (1374 lines)
└── types/

After:
src/
├── utils/
│   ├── conflictUtils.ts (114 lines)
│   ├── systemCompatibility.ts (125 lines)
│   └── versionUtil.ts (enhanced)
└── workbench/extensions/manager/
    ├── composables/
    │   ├── useConflictDetection.ts (758 lines)
    │   └── [other composables]
    └── stores/
        └── conflictDetectionStore.ts
```

### Testing

All tests pass successfully:
-  **155 test files passed**
-  **2209 tests passed**
-  19 skipped (intentionally skipped subgraph-related tests)

### Impact

- **Better code organization** - Manager-specific code is now properly
isolated
- **Improved maintainability** - Smaller, focused utility functions are
easier to test and maintain
- **Enhanced type safety** - No more `any` types in tests
- **Comprehensive test coverage** - All utility functions are thoroughly
tested

### Commits in this PR
1. OS detection bug fix and refactor
2. Remove unused system environment code
3. Improve function naming
4. Refactor conflict detection
5. Remove duplicate state and unused functions
6. Fix unused function exports
7. Move manager features to workbench extension folder
8. Fix import paths
9. Rename systemCompatibility file
10. Improve test type safety
11. Apply ESLint and Prettier fixes

## Screenshots (if applicable)

[screen-capture.webm](https://github.com/user-attachments/assets/b4595604-3761-4d98-ae8e-5693cd0c95bd)


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5436-Manager-refactor-conflict-detect-2686d73d36508186ba06f57dae3656e5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-26 12:21:05 +09:00
Christian Byrne
78d585eca0 fix restoring outputs in Vue nodes (persisting node outputs when switching between workflow tabs) (#5788)
## Summary

Fixed Vue node output restoration by consolidating state management
through the node output store.

## Changes

- **What**: Refactored node output cleanup and restoration logic in
`ChangeTracker` and `ComfyApp` to use centralized store methods
- **Breaking**: Removed direct manipulation of `app.nodeOutputs` in
favor of store-managed state

## Review Focus

State synchronization between `app.nodeOutputs` and `nodeOutputs.value`
during restore/reset operations, ensuring Vue reactivity is maintained.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5788-fix-restoring-outputs-in-Vue-nodes-persisting-node-outputs-when-switching-between-workfl-27a6d73d365081b39411fed7479d8ac5)
by [Unito](https://www.unito.io)
2025-09-25 19:49:49 -07:00
Christian Byrne
a6600aa109 Use localized labels for Vue node slots and inputs/outputs (fallback to names) (#5773)
## Summary

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/5697 by
adding internationalization support for Vue node widget labels and
output slot names with fallback to original names.

## Changes

- **What**: Added `label` field to widget data structures and
`localized_name` support for output slots
- **Breaking**: None - all changes maintain backward compatibility with
fallback to original names

## Review Focus

Translation key resolution for node definitions and proper fallback
chain for widget labels and slot names.

## Screenshots (if applicable)

Observe that there is parity between Litegraph and Vue nodes w.r.t.
localization of labels/names.

<img width="3455" height="1417" alt="Screenshot from 2025-09-25
13-19-54"
src="https://github.com/user-attachments/assets/93ee8fae-3671-4175-a941-6462f942d0f1"
/>

<img width="3455" height="1417" alt="Screenshot from 2025-09-25
13-19-38"
src="https://github.com/user-attachments/assets/9573eb86-d74a-40ed-a0b5-e30afd3da6eb"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5773-Use-localized-labels-for-Vue-node-slots-and-inputs-outputs-fallback-to-names-2796d73d365081e08fb7d38464f4aa90)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-09-25 19:47:18 -07:00
Christian Byrne
c033e9e4d7 Add theme-aware styling for Vue node tooltips (#5786)
Adds light theme colors from the
[designs](https://www.figma.com/design/31uH3r4x3xbIctuRWYW6NM/V3---Nodes?node-id=6267-16837&m=dev).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5786-Add-theme-aware-styling-for-Vue-node-tooltips-27a6d73d36508167979fe797efcbe3c0)
by [Unito](https://www.unito.io)
2025-09-25 19:08:22 -07:00
Christian Byrne
ac93a6ba3f Disable number grouping (thousands comma separators) by default in Vue node number widgets (#5776)
## Summary

Makes the
[useGrouping](https://primevue.org/inputnumber/#api.inputnumber.props.useGrouping)
prop for number widgets disabled by default, aligning with the old UI
(also requested via design). Node authors can still enable if they want
by setting prop explicitly.

## Changes

- **What**: Modified
[WidgetInputNumberInput](https://primevue.org/inputnumber/) to disable
`useGrouping` by default, requiring explicit opt-in via widget options
- **Testing**: Added component tests covering value binding, component
rendering, step calculations, and grouping behavior

## Review Focus

UX impact on existing nodes that may have relied on default grouping
behavior and test coverage for edge cases with precision calculations.

## Screenshots (if applicable)

*Before*:

<img width="1685" height="879" alt="Screenshot from 2025-09-25 11-34-34"
src="https://github.com/user-attachments/assets/432097ab-203d-4f86-8ca0-721b27ee33de"
/>

*After*:

<img width="1951" height="1175" alt="Screenshot from 2025-09-25
11-35-27"
src="https://github.com/user-attachments/assets/74d35b62-612e-4dbf-b6e2-0ac17af03ea1"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5776-Disable-number-grouping-thousands-comma-separators-by-default-in-Vue-node-number-widget-2796d73d365081369ca6c155335d0d57)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <448862+DrJKL@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
2025-09-25 17:46:46 -07:00
filtered
961af8731e Split Tailwind utility functions out to a shared package (#5777)
## Summary

Split tailwind utils out to a shared package within the monorepo.

## Changes

- Creates `@comfyorg/tailwind-utils` package
- Does not require export, publishing, etc
- Uses `export` to ensure this change does not impact other PRs (many
imports to update)
- If we _want_ to update all imports, there are two commits ready to be
re-applied
  - e.g. `git revert 80960c2a82c0d1ac06eee1bb83ac333216b2b376`

## Review Focus

- Is this pattern desirable?
- Should we just include this in a broader design-system split? I kind
of vote yes, but also it's a good small, first step.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5777-Split-Tailwind-utility-functions-out-to-a-shared-package-2796d73d3650815f976fc73b4fb86ef3)
by [Unito](https://www.unito.io)
2025-09-25 16:55:21 -07:00
Simula_r
703de3e669 Fix/vue nodes markdown (#5771)
## Summary

Improve markdown node by making the textarea match the rendered output
width and height.

## Screenshots (if applicable)


https://github.com/user-attachments/assets/4701f947-0a4f-40f3-83c0-94e53cd10106


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5771-Fix-vue-nodes-markdown-2796d73d365081129428fe9c38178cc4)
by [Unito](https://www.unito.io)
2025-09-25 13:21:17 -07:00
Christian Byrne
415ebfd67b Add muted state to Vue nodes (#5770)
## Summary

Added mute state support to Vue nodes with visual feedback and keyboard
shortcut functionality.

## Changes

- **What**: Implemented mute state (mode 2) for Vue nodes with opacity
styling and `Ctrl+M` hotkey support

## Review Focus

Visual consistency between bypass and mute states, and keyboard shortcut
conflict detection with existing hotkeys.

## Test Coverage

- Single node mute/unmute with `Ctrl+M` hotkey
- Multi-selection mute/unmute operations
- Visual state verification with opacity changes

## Related

- https://github.com/Comfy-Org/ComfyUI_frontend/pull/5715

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5770-Add-muted-state-to-Vue-nodes-2796d73d36508143b3edfbcb782de7c1)
by [Unito](https://www.unito.io)
2025-09-25 13:07:29 -07:00
Alexander Brown
97542efc9b Refactor: Viewport Culling improvements (#5767)
## Summary

Reduce the number of DOM changes, but also restructure the logic to
create a clean point of separation for when we can make DragAndScale
reactive.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5767-Refactor-Viewport-Culling-improvements-2796d73d365081708f02cb8033aac9b1)
by [Unito](https://www.unito.io)
2025-09-25 11:23:15 -07:00
Arjan Singh
13ce23399c Asset Browser Design Review + Filters (#5737)
## Summary

Fixed design feedback and wired up the filter controls.

## Review Focus

Design Feedback:
-
[4872888](48728881af)
-
[9a0b63e](9a0b63edce)

Filters Hookup:
-
[07f22f8](07f22f8074)

Misc (can focus less on):
- claude guidance:
[23e6fa9](23e6fa9723)
- test helpers:
[7801ed9](7801ed9e28)

## Screenshots (if applicable)
<img width="1534" height="1175" alt="Screenshot 2025-09-23 at 1 03
12 PM"
src="https://github.com/user-attachments/assets/d82088e4-7d72-4c6f-904e-5180774d64a5"
/>

<img width="1794" height="793" alt="Screenshot 2025-09-23 at 1 03 22 PM"
src="https://github.com/user-attachments/assets/56eac2ba-5ecc-4a20-843f-ce683dea668c"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5737-Asset-Browser-Design-Review-Filters-2776d73d3650813e890bd16fa6a0433f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2025-09-25 11:17:26 -07:00
Christian Byrne
a0c06bd723 [test] add browser test for missing vue nodes error state (#5768)
## Summary

Added browser test to verify Vue nodes display error state when workflow
contains missing/unknown nodes, complementing

- https://github.com/Comfy-Org/ComfyUI_frontend/pull/5758

## Changes

- **What**: Added [Playwright
test](https://playwright.dev/docs/writing-tests) for Vue nodes error
state handling with missing nodes
- **Test Coverage**: Validates `border-error` class application on nodes
with `UNKNOWN NODE` text

## Review Focus

Test reliability when loading workflows with missing nodes and dialog
interaction timing.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5768-test-add-browser-test-for-missing-vue-nodes-error-state-2796d73d365081aea187cdbc7920a643)
by [Unito](https://www.unito.io)
2025-09-25 11:16:19 -07:00
Christian Byrne
3fc17ebdac [refactor] Migrate manager code from src/composables to src/workbench/extensions/manager (2/2) (#5722)
## Summary

Continuation of

- https://github.com/Comfy-Org/ComfyUI_frontend/pull/5662

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5722-refactor-Migrate-manager-code-from-src-composables-to-src-workbench-extensions-manag-2766d73d36508165a4f5e1940967248f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-09-25 02:40:04 +00:00
Comfy Org PR Bot
0db2a2c03e 1.28.2 (#5766)
Patch version increment to 1.28.2

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5766-1-28-2-2796d73d365081d6bcd9e4ee06f75d5f)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
2025-09-24 21:20:53 -05:00
Arjan Singh
b96bf3871c Fixes nits for #5758 (#5763)
## Summary

1. Fixes nits for #5758
2. Adds some git ignores

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5763-Fixes-nits-for-5758-2796d73d36508192b3e6feecfcacf38b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-24 20:45:33 -05:00
Benjamin Lu
bc4549244e test(e2e): align test default menu to Top; make legacy specs explicit (#5746)
## Summary
UseNewMenu has been defaulted to Top in the app for over a year;
Playwright’s test default lagged behind. This PR aligns the test default
with reality and keeps legacy specs stable.

## Changes
- tests(e2e): default to 'Top' via fixture; specs that previously relied
on the old implicit default now explicitly set 'Comfy.UseNewMenu' to
'Disabled'.
- docs(browser-tests): remove outdated README note suggesting tests set
'Top' manually.

## Review Focus
- Intentional uses of 'Top' and 'Bottom' remain unchanged.
- Confirm ComfyPage default remains 'Top' (see
browser_tests/fixtures/ComfyPage.ts).

## Screenshots (if applicable)
N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5746-test-e2e-align-test-default-menu-to-Top-make-legacy-specs-explicit-2786d73d365081218d06c1346f3ae18e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-24 18:43:22 -07:00
Simula_r
f3e68804e8 fix: prevent pointer events on widgets when in LOD (#5762)
## Summary

Prevent pointer events on widgets themselves when isLOD.

Fixes:
https://www.notion.so/comfy-org/LOD-state-allows-pointer-events-on-widgets-2786d73d3650800da600d7b52c66475a?source=copy_link

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5762-fix-prevent-pointer-events-on-widgets-when-in-LOD-2796d73d365081ac95d4ec27eda5027c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: JakeSchroeder <jake@axiom.co>
2025-09-24 17:53:32 -07:00
Arjan Singh
1749cfa678 [fix] properly show error states (#5758)
## Summary

I want to take a more general look at `comfyApp.graph.onTrigger` but
this is the cleanest fix I could come up with for #5694.

I will explore simplifying onTrigger in a separate PR.

## Changes

1. Create a `node:slot-errors:changed` trigger.
2. Trigger it if we find any of the node slots have errors.
3. Check each node to see if there is any error present.
4. Add an error class if there are.

## Screenshots (if applicable)

Working error states!

<img width="1049" height="987" alt="Screenshot 2025-09-23 at 8 40 04 PM"
src="https://github.com/user-attachments/assets/30e13283-129c-4d9c-b342-e7037582998a"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5758-fix-properly-show-error-states-2786d73d365081cbbf62c314c7f5f380)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-09-24 16:34:39 -07:00
Alexander Brown
0fea54c542 Typing: Slots in VueNodeData (#5759)
## Summary

Replace the unknown type with the interface in Litegraph.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5759-Typing-Slots-in-VueNodeData-2786d73d36508194b286fad172b13c51)
by [Unito](https://www.unito.io)
2025-09-24 14:35:14 -07:00
filtered
cd7666e3bc Update desktop docs to platform-specific URLs (#5757)
Fixes #5751

## Summary
- Replaces outdated Notion link with docs.comfy.org URLs
- Uses Electron API to detect platform for Windows/macOS specific docs

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5757-Update-desktop-docs-to-platform-specific-URLs-2786d73d36508188a200ed3ad91b836f)
by [Unito](https://www.unito.io)
2025-09-24 14:28:53 -07:00
snomiao
8d1261133a [bugfix] Stabilize flaky load audio widget test (#5755)
## Summary
This PR fixes a flaky test in the load audio widget spec that was
causing intermittent failures in CI.

## Problem
The test was attempting to trigger a file chooser event before the
widget's upload button was fully rendered and ready for interaction,
leading to race conditions.

## Solution
Added an explicit wait for the widget element to be visible before
triggering the file chooser, ensuring the DOM is ready for interaction.

## Changes
- Added `waitFor` to verify widget visibility before file upload
- Ensures proper synchronization between DOM updates and test actions

## Test plan
- [x] Browser tests pass locally
- [x] Typecheck passes
- [ ] CI tests pass

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5755-bugfix-Stabilize-flaky-load-audio-widget-test-2786d73d365081cebaefdc6470333c5d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-24 14:27:46 -07:00
Simula_r
0919856a05 Feat/vue nodes preview (#5747)
## Summary

Create a LGraphNodePreview.vue component to use Vue Nodes for preview
when hovering over search results / sidebar tree list.

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<img width="3024" height="1642" alt="image"
src="https://github.com/user-attachments/assets/d102b08e-2970-407b-aff8-3fa6333d5e38"
/>
<img width="3024" height="1646" alt="image (1)"
src="https://github.com/user-attachments/assets/b5d378d5-3cf6-4cca-9fa1-741647e8d72c"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5747-Feat-vue-nodes-preview-2786d73d3650817dbf9af458bd5dda8c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: JakeSchroeder <jake@axiom.co>
Co-authored-by: AustinMroz <AustinMroz@users.noreply.github.com>
2025-09-24 13:02:47 -07:00
filtered
b0f81b2245 Rework desktop install / startup UX (#5292)
### Summary

Complete redesign of Desktop app installer, implementing the new app
startup progress system and error reporting.
2025-09-24 12:06:58 -07:00
Alexander Brown
6449d26cee cleanup: remove useCanvasTransformSync composables. (#5742)
No I am not proud of the new placeholder arguments.

## Summary

Small change to unify the two composables before updating the logic.
Edit: Now unifies them both into the **void**.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5742-cleanup-unify-useCanvasTransformSync-composables-2776d73d36508147ad39d11de8588b2e)
by [Unito](https://www.unito.io)
2025-09-23 21:36:29 -07:00
Tristan Sommer
80cabc61ee fix: maskeditor - fixed color select and paint bucket settings not showing up (#5733)
## Summary

color select settings and paint bucket settings were not showing up -
fixed that

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5733-fix-maskeditor-fixed-color-select-and-paint-bucket-settings-not-showing-up-2776d73d365081e6be2ddc7784ab8535)
by [Unito](https://www.unito.io)
2025-09-23 16:27:16 -07:00
Arjan Singh
76dd935b35 [fix] use object-contain for image preview (#5739)
## Summary

Change css so preview images are fully contained within the image
preview component

## Screenshots (if applicable)

### Before

<img width="453" height="640" alt="Screenshot 2025-09-23 at 2 55 56 PM"
src="https://github.com/user-attachments/assets/892c7fd4-b9d9-4fdd-9846-d480c5aeb6be"
/>

### After

<img width="523" height="673" alt="Screenshot 2025-09-23 at 2 55 26 PM"
src="https://github.com/user-attachments/assets/ca3f4823-9d57-4bf4-93cc-492652ee9631"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5739-fix-use-object-contain-for-image-preview-2776d73d365081f3b77ce498bd8799ec)
by [Unito](https://www.unito.io)
2025-09-23 16:04:54 -07:00
Alexander Brown
f7f3240100 fix: Status indicator and close button appearing together (#5738)
## Summary

Small fix for the close/status visibility overlap

<img width="392" height="128" alt="image"
src="https://github.com/user-attachments/assets/af25f1d7-a8c3-4155-9123-9fa10724e8db"
/>

![20250923-2140-26
4569642](https://github.com/user-attachments/assets/e1b00a3f-d6e9-416b-9014-df0f9241082e)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5738-fix-Status-indicator-and-close-button-appearing-together-2776d73d3650813e9601e519c8a85043)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-23 15:23:00 -07:00
Simula_r
e4022c455a fix: add LODFallback to markdown widget (#5734)
## Summary

Add LOD fallback to markdown widget

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5734-fix-add-LODFallback-to-markdown-widget-2776d73d36508128b923d45beab50f03)
by [Unito](https://www.unito.io)

---------

Co-authored-by: JakeSchroeder <jake@axiom.co>
2025-09-23 13:05:15 -07:00
filtered
c6e50d8f1b Fix overlapping elements in desktop installer (#5735)
Fixes regression from Tailwind v4 upgrade where CPU toggle overlaps
content.

Removed unnecessary translate transform causing element positioning
issues.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5735-Fix-overlapping-elements-in-desktop-installer-2776d73d365081299affc7894bc88faa)
by [Unito](https://www.unito.io)
2025-09-23 14:50:44 -05:00
snomiao
c004a2b8bd chore(tsconfig): ensure complete TypeScript coverage for all project files (#5655)
## Summary
- Added missing directories and files to tsconfig.json to ensure
complete TypeScript type checking coverage
- Expanded config file patterns to include all .mts configuration files
- Verified all 908 TypeScript files in the project are now properly
covered

## Changes
- Added `scripts/**/*.ts` to cover all TypeScript files in scripts
directory (i18n collection, CI/CD scripts)
- Added `build/**/*.ts` to cover customIconCollection.ts and future
build scripts
- Changed `vite.config.mts` to `*.config.mts` to include all vite config
files (vite.electron.config.mts, vite.types.config.mts)

## Test plan
- [x] Run `pnpm typecheck` to verify no TypeScript errors
- [x] Verified all TypeScript files are covered by tsconfig patterns
- [x] browser_tests/ directory confirmed to have its own extending
tsconfig

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5655-chore-tsconfig-ensure-complete-TypeScript-coverage-for-all-project-files-2736d73d36508103acbadc53ca2b2913)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-23 09:31:30 -07:00
snomiao
d0aee031e9 [feat] Merge ComfyUI_devtools into ComfyUI_frontend (#5166)
## Summary
Merges ComfyUI devtools components into the ComfyUI frontend monorepo to
consolidate development tools.

## Changes
- Added devtools components from ComfyUI repository
- Integrated development nodes and utilities
- Consolidated fake model assets for testing

## Related Issues
Fixes #4683

## Testing
- Devtools components are now available within the frontend monorepo
- Development workflow remains consistent

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-22 23:15:50 -07:00
Simula_r
cec1de0147 feat: vue nodes LOD system (#5631)
## Summary

Replaced reactive (Vue-based) widget LOD with CSS visibility control.
Performance doesn't dramatically improve, but we avoid the mount/unmount
overhead during zoom/pan operations. This PR implements the visual
component of LOD—complex widgets that need lifecycle management will be
addressed separately.

### Problem & Solution
Problem: we want LOD to improve rendering performance and visual
feedback but discovered using reactivity in the current setup for it
meant mounting/unmounting caused worse lag than the performance it aimed
to fix. Switching to render all the details all the time but using css
visibility proved to be the best solution. However, it doesn't improve
rendering performance by much because the GPU texture size is the
bottleneck (from TransformPane.vue CSS transforms) and not
rasterization.

Solution: Keep all nodes/widgets mounted, use CSS visibility: hidden for
LOD. Trade memory for performance stability during zoom/pan/drag
operations.

### Technical Decision
We chose Performance > Memory:

- CSS transforms create a single GPU texture whose size depends on node
count, not widget complexity
- Mounting/unmounting hundreds of widgets during zoom = noticeable lag
from Vue VDOM diffing (since all components are mounted all the time
because of viewport culling challenge/trade off see
https://github.com/Comfy-Org/ComfyUI_frontend/pull/5510.)
- CSS visibility changes = no reactivity overhead, smooth interactions
- Result: Similar performance, but without interaction stutters

This is the visual layer only. If we want a hook into the LOD state per
node / widget that would be the next follow up system to implement.

### Next Steps (maybe)
- Chunked (split up single Transform Pane transform layer) when
rendering 1000+ nodes (maybe)
- ~~Selective unmounting API for widgets that register as "expensive"~~
- ~~Client bound hydration system~~

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

<img width="1355" height="960" alt="image"
src="https://github.com/user-attachments/assets/41474d1b-9dbe-4240-a8cf-f4c9ff51d8e0"
/>
<img width="1354" height="963" alt="image"
src="https://github.com/user-attachments/assets/9f55edaa-5858-41b9-b6a8-c2d37e1649bd"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5631-feat-vue-nodes-LOD-system-2726d73d365081c6a6c4e14aa634f19c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-22 20:05:13 -07:00
Christian Byrne
b4976c1ddc Revert: Move VueFire persistence configuration to initialization (#5614) (#5729)
## Summary
This reverts PR #5614 which moved VueFire persistence configuration to
initialization.

## Reason for Revert

It breaks Google SSO login with error:

```
useErrorHandling.ts:12 FirebaseError: Firebase: Error (auth/argument-error).
    at createErrorInternal (index-c92d61ad.js:506:41)
    at _assert (index-c92d61ad.js:512:15)
    at _withDefaultResolver (index-c92d61ad.js:9237:5)
    at signInWithPopup (index-c92d61ad.js:9457:30)
    at executeAuthAction.createCustomer (firebaseAuthStore.ts:263:25)
    at executeAuthAction (firebaseAuthStore.ts:223:28)
    at Proxy.loginWithGoogle (firebaseAuthStore.ts:262:5)
    at Proxy.wrappedAction (pinia.mjs:1405:26)
    at useFirebaseAuthActions.ts:104:28
    at Object.signInWithGoogle (useErrorHandling.ts:39:22)
```

## Changes
- Reverts commit ea4e57b60 "Move VueFire persistence configuration to
initialization (#5614)"
- Restores previous Firebase auth persistence behavior

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5729-Revert-Move-VueFire-persistence-configuration-to-initialization-5614-2776d73d3650814c9b80d9c67c852874)
by [Unito](https://www.unito.io)
2025-09-22 19:04:08 -07:00
Alexander Brown
1611c7a224 Refactor: Further state management cleanup (#5727)
## Summary

Going through the GraphNodeManager and VueNodeLifecycle one property at
a time and removing the pieces that are not currently wired up or used
by the rest of the application

Fixes paste location by updating the layoutStore in LGraphCanvas (which
already mutates layoutStore elsewhere)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5727-WIP-Refactor-Further-state-management-cleanup-2766d73d36508173b379c6009c194a5a)
by [Unito](https://www.unito.io)
2025-09-22 18:47:26 -07:00
Comfy Org PR Bot
d01081dab4 1.28.1 (#5728)
Patch version increment to 1.28.1

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5728-1-28-1-2776d73d365081b287e4fdc5cbcea07b)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
2025-09-22 20:36:12 -05:00
Alexander Brown
e5d4d07d32 Refactor: More state management simplification (#5721)
## Summary

Remove more procedural synchronization in favor of using reactive
references.

> Note: Also includes some fixes for issues caused during HMR.

## Review Focus

In testing it seems to work the same, but let me know if I missed
something.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5721-Refactor-More-state-management-simplification-2766d73d3650819b8d7ddc047c460f2b)
by [Unito](https://www.unito.io)
2025-09-22 13:15:33 -07:00
Alexander Piskun
f086377307 add pricing for new api nodes (#5724)
## Summary

Added prices for the new upcoming API nodes. Backport required.
2025-09-22 11:33:00 -07:00
filtered
687b9e659c Fix reroute ID 0 treated as invalid (#5723)
## Summary

Fixes old logic bug from refactor
https://github.com/Comfy-Org/litegraph.js/pull/602/files

## Changes

- Fixes truthy refactor to explicitly check undefined

## Review Focus

No expectation that this will impact prod, however it may impact
extensions IF someone has explicitly been setting link parentId to 0.
This would be very strange, as it would cause unexpected behaviour in
other parts of the code (which all explicitly check `undefined`).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5723-Fix-reroute-ID-0-treated-as-invalid-2766d73d365081568124ce1f85cdf84e)
by [Unito](https://www.unito.io)
2025-09-22 11:13:38 -07:00
Christian Byrne
da0d51311b fix Vue node being dragged when interacting with widgets (e.g., resizing textarea) (#5719)
## Summary

Applying changes in
https://github.com/Comfy-Org/ComfyUI_frontend/pull/5516 to entire widget
wrapper.
 
## Changes

- **What**: Added `.stop` modifier to pointer events in NodeWidgets
component to prevent [event
propagation](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation)

## Review Focus

Verify widget interactions remain functional while ensuring parent node
drag/selection behavior is properly isolated.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5719-fix-Vue-node-being-dragged-when-interacting-with-widgets-e-g-resizing-textarea-2766d73d3650815091adcd1d65197c7b)
by [Unito](https://www.unito.io)
2025-09-21 21:56:03 -07:00
Christian Byrne
e314d9cbd9 [refactor] Simplify current user resolved hook implementation (#5718)
## Summary

Refactored `onUserResolved` function in auth composable to use VueUse
`whenever` utility instead of manual watch implementation and use
`immediate` option instead of invoking manually before creating watcher.

## Changes

- **What**: Replaced manual watch + immediate check pattern with [VueUse
whenever](https://vueuse.org/shared/whenever/) utility in
`useCurrentUser.ts:37`

## Review Focus

Behavioral equivalence verification - `whenever` with `immediate: true`
should maintain identical callback timing and cleanup semantics.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5718-refactor-Simplify-current-user-resolved-hook-implementation-2766d73d365081008b6de156dd78f940)
by [Unito](https://www.unito.io)
2025-09-21 21:53:25 -07:00
Christian Byrne
95baf8d2f1 [style] update Vue node tooltip style (#5717)
## Summary

Change Vue node tooltips to align with
[design](https://www.figma.com/design/31uH3r4x3xbIctuRWYW6NM/V3---Nodes?node-id=6267-16837&m=dev)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5717-style-update-Vue-node-tooltip-style-2766d73d365081bdb095faef17f6aeb6)
by [Unito](https://www.unito.io)
2025-09-21 20:01:33 -07:00
Christian Byrne
f951e07cea fix bypass hotkey in vue nodes and fix node data instrumentation setup issue when switching to Vue nodes after initial load (#5715)
## Summary

Fixed Vue node keybinding target element ID to enable
bypass/pin/collapse hotkeys in both LiteGraph and Vue rendering modes.

Also fixed a bug when starting in litegraph mode => switching to Vue
nodes without reloading => `graph.onTrigger` is set to `undefined` which
interferes with proper setup of node data instrumentation, among other
things.

## Changes

- **What**: Updated keybinding `targetElementId` from `graph-canvas` to
`graph-canvas-container` for node manipulation commands (parent of both
the canvas and transform pane -- vue nodes container).
- **What**: Added conditional `onTrigger` handler restoration in slot
layout sync to prevent Vue node manager conflicts

## Review Focus

Event handler precedence between Vue nodes and LiteGraph systems during
mode switching, ensuring hotkeys work consistently across rendering
modes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5715-fix-bypass-hotkey-in-vue-nodes-and-fix-node-data-instrumentation-setup-issue-when-switchi-2756d73d3650815c8ec8d5e4d06232e3)
by [Unito](https://www.unito.io)
2025-09-21 17:32:12 -07:00
Christian Byrne
023e466dba fix using shift modifier to (de-)select Vue nodes (#5714)
## Summary

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/5688 by
adding shift modifier support for multi-selecting Vue nodes, enabling
standard shift+click selection behavior alongside existing
ctrl/cmd+click.

## Changes

- **What**: Updated Vue node event handlers to include `event.shiftKey`
in multi-select logic
- **Testing**: Added browser tests for both ctrl and shift modifier
selection behaviors

## Review Focus

Multi-select behavior consistency across different input modifiers and
platform compatibility (Windows/Mac/Linux shift key handling).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5714-fix-using-shift-modifier-to-de-select-Vue-nodes-2756d73d365081bcb5e0fe80eacdb2f0)
by [Unito](https://www.unito.io)
2025-09-21 14:39:40 -07:00
Christian Byrne
abd6823744 [refactor] Remove redundant module comment (#5711)
Removes a comment added in initial Vue Nodes commit. The comment is
interpolated between import statements which is stylistically awkward
and it is almost totally redundant with the doc comment on the
composable:


c1d4709e96/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts (L10-L14)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5711-refactor-Remove-redundant-module-comment-2756d73d365081ef9bffe0257b3670f1)
by [Unito](https://www.unito.io)
2025-09-21 14:30:58 -07:00
Alexander Brown
c4c0e52e64 Refactor: Let LGraphNode handle more events itself (#5709)
## Summary

Don't route events up through GraphCanvas if the component itself can
handle the changes

## Changes

- **What**: Reduce the indirect access or action dispatch to
composables/stores.

## Review Focus

The behavior should be either equivalent or a little snappier than
before. Also, the local state in LGraphNode has (almost) all been
removed in favor of reacting to the nodeData prop.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5709-Refactor-Let-LGraphNode-handle-more-events-itself-2756d73d365081e6a88ce6241bceecc0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-09-20 22:14:30 -07:00
Christian Byrne
295332dc46 update CODEOWNERS (#5667)
Add explicit CODEOWNERS for new features to allow more domain-driven
review/approval/ownership processes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5667-update-CODEOWNERS-2736d73d3650817ea52be9c4a8fe5ff2)
by [Unito](https://www.unito.io)
2025-09-20 20:11:35 -07:00
Christian Byrne
5c498348b8 fix: update to standardized mobile web app meta tag syntax (#5672)
## Summary

Fixed WebKit deprecation warning by updating to standardized mobile web
app meta tag syntax.

## Changes

- **What**: Replaced deprecated `apple-mobile-web-app-capable` with
cross-platform
[`mobile-web-app-capable`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#mobile-web-app-capable)
meta tag to align with WebKit's move toward vendor-neutral standards

## Review Focus

Verify "Add to Home Screen" functionality still works on iOS/iPadOS and
that the WebKit console warning is resolved in production builds.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5672-fix-update-to-standardized-mobile-web-app-meta-tag-syntax-2736d73d3650811cb2a1f0b14ce0a0e7)
by [Unito](https://www.unito.io)
2025-09-20 20:10:51 -07:00
Alexander Brown
8133bd4b7b Refactor: Composable disentangling (#5695)
## Summary

Prerequisite refactor/cleanup to use a global store instead of having
nodes throw up events to a parent component that stores a reference to a
singleton service that itself bootstraps and synchronizes with a
separate service to maintain a partially reactive but not fully reactive
set of states that describe some but not all aspects of the nodes on
either the litegraph, the vue side, or both.

## Changes

- **What**: Refactoring, the behavior should not change.
- **Dependencies**: A type utility to help with Vue component props

## Review Focus

Is there something about the current structure that this could affect
that would not be caught by our tests or using the application?

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5695-Refactor-Composable-disentangling-2746d73d365081e6938ce656932f3e36)
by [Unito](https://www.unito.io)
2025-09-20 13:06:42 -07:00
Arjan Singh
fd12591756 [feat] integrate asset browser with widget system (#5629)
## Summary

Add asset browser dialog integration for combo widgets with full
animation support and proper state management.

(Thank you Claude from saving me me from merge conflict hell on this
one.)

## Changes

- Widget integration: combo widgets now use AssetBrowserModal for
eligible asset types
- Dialog animations: added animateHide() for smooth close transitions
- Async operations: proper sequencing of widget updates and dialog
animations
- Service layer: added getAssetsForNodeType() and getAssetDetails()
methods
- Type safety: comprehensive TypeScript types and error handling
- Test coverage: unit tests for all new functionality
- Bonus: fixed the hardcoded labels in AssetFilterBar

Widget behavior:
- Shows asset browser button for eligible widgets when asset API enabled
- Handles asset selection with proper callback sequencing
- Maintains widget value updates and litegraph notification

## Review Focus

I will call out some stuff inline.

## Screenshots


https://github.com/user-attachments/assets/9d3a72cf-d2b0-445f-8022-4c49daa04637

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5629-feat-integrate-asset-browser-with-widget-system-2726d73d365081a9a98be9a2307aee0b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2025-09-20 11:44:18 -07:00
Christian Byrne
b3c939ff15 fix: add Safari requestIdleCallback polyfill (#5664)
## Summary

Implemented cross-browser requestIdleCallback polyfill to fix Safari
crashes during graph initialization.

## Changes

- **What**: Added
[requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
polyfill following [VS Code's
pattern](https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts)
with setTimeout fallback for Safari
- **Breaking**: None - maintains existing GraphView behavior

## Review Focus

Safari compatibility testing and timeout handling in the 15ms fallback
window. Verify that initialization tasks (keybindings, server config,
model loading) still execute properly on Safari iOS.

## References

- [VS Code async.ts
implementation](https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts)
- Source pattern for our polyfill
- [MDN
requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
- Browser API documentation
- [Safari requestIdleCallback
support](https://caniuse.com/requestidlecallback) - Browser
compatibility table

Fixes CLOUD-FRONTEND-STAGING-N

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5664-fix-add-Safari-requestIdleCallback-polyfill-2736d73d365081cdbcf1fb816fe098d6)
by [Unito](https://www.unito.io)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-19 23:34:15 -07:00
387 changed files with 14550 additions and 9179 deletions

View File

@@ -32,11 +32,10 @@ jobs:
with:
repository: Comfy-Org/ComfyUI_frontend
path: ComfyUI_frontend
- name: Checkout ComfyUI_devtools
uses: actions/checkout@v4
with:
repository: Comfy-Org/ComfyUI_devtools
path: ComfyUI/custom_nodes/ComfyUI_devtools
- name: Copy ComfyUI_devtools from frontend repo
run: |
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
- name: Checkout custom node repository
uses: actions/checkout@v4
with:
@@ -79,7 +78,7 @@ jobs:
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
@@ -87,7 +86,7 @@ jobs:
run: pnpm dev:electron &
working-directory: ComfyUI_frontend
- name: Capture base i18n
run: npx tsx scripts/diff-i18n capture
run: pnpm exec tsx scripts/diff-i18n capture
working-directory: ComfyUI_frontend
- name: Update en.json
run: pnpm collect-i18n
@@ -100,7 +99,7 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Diff base vs updated i18n
run: npx tsx scripts/diff-i18n diff
run: pnpm exec tsx scripts/diff-i18n diff
working-directory: ComfyUI_frontend
- name: Update i18n in custom node repository
run: |

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server

View File

@@ -33,7 +33,7 @@ jobs:
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server

View File

@@ -19,11 +19,11 @@ jobs:
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests and update snapshots
id: playwright-tests
run: npx playwright test --update-snapshots
run: pnpm exec playwright test --update-snapshots
continue-on-error: true
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4

View File

@@ -27,12 +27,10 @@ jobs:
repository: 'Comfy-Org/ComfyUI_frontend'
path: 'ComfyUI_frontend'
- name: Checkout ComfyUI_devtools
uses: actions/checkout@v4
with:
repository: 'Comfy-Org/ComfyUI_devtools'
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
- name: Copy ComfyUI_devtools from frontend repo
run: |
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -150,7 +148,7 @@ jobs:
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
id: playwright
run: npx playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
working-directory: ComfyUI_frontend
env:
PLAYWRIGHT_BLOB_OUTPUT_DIR: ../blob-report
@@ -232,7 +230,7 @@ jobs:
run: |
# Run tests with both HTML and JSON reporters
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
npx playwright test --project=${{ matrix.browser }} \
pnpm exec playwright test --project=${{ matrix.browser }} \
--reporter=list \
--reporter=html \
--reporter=json
@@ -283,10 +281,10 @@ jobs:
- name: Merge into HTML Report
run: |
# Generate HTML report
npx playwright merge-reports --reporter=html ./all-blob-reports
pnpm exec 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
pnpm exec playwright merge-reports --reporter=json ./all-blob-reports
working-directory: ComfyUI_frontend
- name: Upload HTML report

View File

@@ -68,7 +68,7 @@ jobs:
- name: Generate Manager API types
run: |
echo "Generating TypeScript types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}..."
npx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts
pnpm dlx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts
- name: Validate generated types
run: |

View File

@@ -68,7 +68,7 @@ jobs:
- name: Generate API types
run: |
echo "Generating TypeScript types from comfy-api@${{ steps.api-info.outputs.commit }}..."
npx openapi-typescript ./comfy-api/openapi.yml --output ./src/types/comfyRegistryTypes.ts
pnpm dlx openapi-typescript ./comfy-api/openapi.yml --output ./src/types/comfyRegistryTypes.ts
- name: Validate generated types
run: |

3
.gitignore vendored
View File

@@ -22,6 +22,8 @@ dist-ssr
*.local
# Claude configuration
.claude/*.local.json
.claude/*.local.md
.claude/*.local.txt
CLAUDE.local.md
# Editor directories and files
@@ -44,6 +46,7 @@ components.d.ts
tests-ui/data/*
tests-ui/ComfyUI_examples
tests-ui/workflows/examples
coverage/
# Browser tests
/test-results/

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
pnpm exec lint-staged
pnpm exec tsx scripts/check-unused-i18n-keys.ts
pnpm exec tsx scripts/check-unused-i18n-keys.ts

View File

@@ -45,7 +45,7 @@ const config: StorybookConfig = {
compiler: 'vue3',
customCollections: {
comfy: FileSystemIconLoader(
process.cwd() + '/src/assets/icons/custom'
process.cwd() + '/packages/design-system/src/icons'
)
}
}),

View File

@@ -1,17 +1,61 @@
# Admins
* @Comfy-Org/comfy_frontend_devs
# Desktop/Electron
/src/types/desktop/ @webfiltered
/src/constants/desktopDialogs.ts @webfiltered
/src/constants/desktopMaintenanceTasks.ts @webfiltered
/src/stores/electronDownloadStore.ts @webfiltered
/src/extensions/core/electronAdapter.ts @webfiltered
/src/views/DesktopDialogView.vue @webfiltered
/src/components/install/ @webfiltered
/src/components/maintenance/ @webfiltered
/vite.electron.config.mts @webfiltered
# Maintainers
*.md @Comfy-Org/comfy_maintainer
/tests-ui/ @Comfy-Org/comfy_maintainer
/browser_tests/ @Comfy-Org/comfy_maintainer
/.env_example @Comfy-Org/comfy_maintainer
# Common UI Components
/src/components/chip/ @viva-jinyi
/src/components/card/ @viva-jinyi
/src/components/button/ @viva-jinyi
/src/components/input/ @viva-jinyi
# Translations (AIGODLIKE team + shinshin86)
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
# Topbar
/src/components/topbar/ @pythongosssss
# Load 3D extension
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
# Thumbnail
/src/renderer/core/thumbnail/ @pythongosssss
# Mask Editor extension
/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs
# Legacy UI
/scripts/ui/ @pythongosssss
# Link rendering
/src/renderer/core/canvas/links/ @benceruleanlu
# Node help system
/src/utils/nodeHelpUtil.ts @benceruleanlu
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu
/src/services/nodeHelpService.ts @benceruleanlu
# Selection toolbox
/src/components/graph/selectionToolbox/ @Myestery
# Minimap
/src/renderer/extensions/minimap/ @jtydhr88
# Assets
/src/platform/assets/ @arjansingh
# Workflow Templates
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki
# Mask Editor
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp
# 3D
/src/extensions/core/load3d.ts @jtydhr88
/src/components/load3d/ @jtydhr88
# Manager
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
# Translations
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer

View File

@@ -265,9 +265,9 @@ The project supports three types of icons, all with automatic imports (no manual
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i-lucide:settings />`, `<i-mdi:folder />`
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/` and processed by `build/customIconCollection.ts` with automatic validation.
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation.
For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md).
For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md).
## Working with litegraph.js

View File

@@ -16,15 +16,20 @@ Without this flag, parallel tests will conflict and fail randomly.
### ComfyUI devtools
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
ComfyUI_devtools is now included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory.
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
For local development, copy the devtools files to your ComfyUI installation:
```bash
cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/
```
### Node.js & Playwright Prerequisites
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
```bash
npx playwright install chromium --with-deps
pnpm exec playwright install chromium --with-deps
```
### Environment Configuration
@@ -51,14 +56,6 @@ TEST_COMFYUI_DIR=/path/to/your/ComfyUI
### Common Setup Issues
**Most tests require the new menu system** - Add to your test:
```typescript
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
```
### Release API Mocking
By default, all tests mock the release API (`api.comfy.org/releases`) to prevent release notification popups from interfering with test execution. This is necessary because the release notifications can appear over UI elements and block test interactions.
@@ -76,7 +73,7 @@ For tests that specifically need to test release functionality, see the example
**Always use UI mode for development:**
```bash
npx playwright test --ui
pnpm exec playwright test --ui
```
UI mode features:
@@ -92,8 +89,8 @@ UI mode features:
For CI or headless testing:
```bash
npx playwright test # Run all tests
npx playwright test widget.spec.ts # Run specific test file
pnpm exec playwright test # Run all tests
pnpm exec playwright test widget.spec.ts # Run specific test file
```
### Local Development Config
@@ -389,7 +386,7 @@ export default defineConfig({
Option 2 - Generate local baselines for comparison:
```bash
npx playwright test --update-snapshots
pnpm exec playwright test --update-snapshots
```
### Creating New Screenshot Baselines

View File

@@ -1643,7 +1643,7 @@ export const comfyPageFixture = base.extend<{
try {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
'Comfy.UseNewMenu': 'Top',
// Hide canvas menu/info/selection toolbox by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,

View File

@@ -22,6 +22,13 @@ export class VueNodeHelpers {
)
}
/**
* Get locator for a Vue node by the node's title (displayed name in the header)
*/
getNodeByTitle(title: string): Locator {
return this.page.locator(`[data-node-id]`).filter({ hasText: title })
}
/**
* Get total count of Vue nodes in the DOM
*/

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Background Image Upload', () => {
test.beforeEach(async ({ comfyPage }) => {
// Reset the background image setting before each test

View File

@@ -15,6 +15,10 @@ async function afterChange(comfyPage: ComfyPage) {
})
}
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Change Tracker', () => {
test.describe('Undo/Redo', () => {
test.beforeEach(async ({ comfyPage }) => {

View File

@@ -3,6 +3,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
interface ChatHistoryEntry {
prompt: string
response: string

View File

@@ -3,6 +3,10 @@ import { expect } from '@playwright/test'
import type { Palette } from '../../src/schemas/colorPaletteSchema'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
const customColorPalettes: Record<string, Palette> = {
obsidian: {
version: 102,

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Keybindings', () => {
test('Should execute command', async ({ comfyPage }) => {
await comfyPage.registerCommand('TestCommand', () => {

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Copy Paste', () => {
test('Can copy and paste node', async ({ comfyPage }) => {
await comfyPage.clickEmptyLatentNode()

View File

@@ -4,6 +4,10 @@ import { expect } from '@playwright/test'
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Load workflow warning', () => {
test('Should display a warning when loading a workflow with missing nodes', async ({
comfyPage

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('DOM Widget', () => {
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('widgets/collapsed_multiline')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Execution', () => {
test('Report error on unconnected slot', async ({ comfyPage }) => {
await comfyPage.disconnectEdge()

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Feature Flags', () => {
test('Client and server exchange feature flags on connection', async ({
comfyPage

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Graph', () => {
// Should be able to fix link input slot index after swap the input order
// Ref: https://github.com/Comfy-Org/ComfyUI_frontend/issues/3348

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Graph Canvas Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
// Set link render mode to spline to make sure it's not affected by other tests'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -4,6 +4,10 @@ import type { ComfyPage } from '../fixtures/ComfyPage'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Group Node', () => {
test.describe('Node library sidebar', () => {
const groupNodeName = 'DefautWorkflowGroupNode'

View File

@@ -9,6 +9,10 @@ import {
} from '../fixtures/ComfyPage'
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Item Interaction', () => {
test('Can select/delete all items', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('groups/mixed_graph_items')

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Keybindings', () => {
test('Should not trigger non-modifier keybinding when typing in input fields', async ({
comfyPage

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
function listenForEvent(): Promise<Event> {
return new Promise<Event>((resolve) => {
document.addEventListener('litegraph:canvas', (e) => resolve(e), {

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Load Workflow in Media', () => {
const fileNames = [
'workflow.webp',

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('LOD Threshold', () => {
test('Should switch to low quality mode at correct zoom threshold', async ({
comfyPage

View File

@@ -4,6 +4,10 @@ import type { ComfyApp } from '../../src/scripts/app'
import { NodeBadgeMode } from '../../src/types/nodeSource'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Node Badge', () => {
test('Can add badge', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
// If an input is optional by node definition, it should be shown as
// a hollow circle no matter what shape it was defined in the workflow JSON.
test.describe('Optional input', () => {

View File

@@ -3,6 +3,10 @@ import {
comfyPageFixture as test
} from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Node search box', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'search box')

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Note Node', () => {
test('Can load node nodes', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('nodes/note_nodes')

View File

@@ -3,6 +3,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Primitive Node', () => {
test('Can load with correct size', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('primitive/primitive_node')

View File

@@ -40,6 +40,7 @@ test.describe('Reroute Node', () => {
test.describe('LiteGraph Native Reroute Node', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.setSetting('LiteGraph.Reroute.SplineOffset', 80)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -3,6 +3,10 @@ import { expect } from '@playwright/test'
import { NodeBadgeMode } from '../../src/types/nodeSource'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Canvas Right Click Menu', () => {
test('Can add node', async ({ comfyPage }) => {
await comfyPage.rightClickCanvas()

View File

@@ -4,6 +4,9 @@ import { comfyPageFixture } from '../fixtures/ComfyPage'
const test = comfyPageFixture
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
const BLUE_COLOR = 'rgb(51, 51, 85)'
const RED_COLOR = 'rgb(85, 51, 51)'

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Selection Toolbox - More Options Submenus', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)

View File

@@ -80,6 +80,12 @@ test.describe('Templates', () => {
// Load a template
await comfyPage.executeCommand('Comfy.BrowseTemplates')
await expect(comfyPage.templates.content).toBeVisible()
await comfyPage.page
.locator(
'nav > div:nth-child(2) > div > span:has-text("Getting Started")'
)
.click()
await comfyPage.templates.loadTemplate('default')
await expect(comfyPage.templates.content).toBeHidden()
@@ -102,48 +108,72 @@ test.describe('Templates', () => {
expect(await comfyPage.templates.content.isVisible()).toBe(true)
})
test('Uses title field as fallback when the key is not found in locales', async ({
test('Uses proper locale files for templates', async ({ comfyPage }) => {
// Set locale to French before opening templates
await comfyPage.setSetting('Comfy.Locale', 'fr')
// Load the templates dialog and wait for the French index file request
const requestPromise = comfyPage.page.waitForRequest(
'**/templates/index.fr.json'
)
await comfyPage.executeCommand('Comfy.BrowseTemplates')
const request = await requestPromise
// Verify French index was requested
expect(request.url()).toContain('templates/index.fr.json')
await expect(comfyPage.templates.content).toBeVisible()
})
test('Falls back to English templates when locale file not found', async ({
comfyPage
}) => {
// Capture request for the index.json
await comfyPage.page.route('**/templates/index.json', async (route, _) => {
// Add a new template that won't have a translation pre-generated
const response = [
{
moduleName: 'default',
title: 'FALLBACK CATEGORY',
type: 'image',
templates: [
{
name: 'unknown_key_has_no_translation_available',
title: 'FALLBACK TEMPLATE NAME',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'No translations found'
}
]
}
]
// Set locale to a language that doesn't have a template file
await comfyPage.setSetting('Comfy.Locale', 'de') // German - no index.de.json exists
// Wait for the German request (expected to 404)
const germanRequestPromise = comfyPage.page.waitForRequest(
'**/templates/index.de.json'
)
// Wait for the fallback English request
const englishRequestPromise = comfyPage.page.waitForRequest(
'**/templates/index.json'
)
// Intercept the German file to simulate a 404
await comfyPage.page.route('**/templates/index.de.json', async (route) => {
await route.fulfill({
status: 200,
body: JSON.stringify(response),
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store'
}
status: 404,
headers: { 'Content-Type': 'text/plain' },
body: 'Not Found'
})
})
// Allow the English index to load normally
await comfyPage.page.route('**/templates/index.json', (route) =>
route.continue()
)
// Load the templates dialog
await comfyPage.executeCommand('Comfy.BrowseTemplates')
await expect(comfyPage.templates.content).toBeVisible()
// Expect the title to be used as fallback for template cards
// Verify German was requested first, then English as fallback
const germanRequest = await germanRequestPromise
const englishRequest = await englishRequestPromise
expect(germanRequest.url()).toContain('templates/index.de.json')
expect(englishRequest.url()).toContain('templates/index.json')
// Verify English titles are shown as fallback
await expect(
comfyPage.templates.content.getByText('FALLBACK TEMPLATE NAME')
comfyPage.templates.content.getByRole('heading', {
name: 'Image Generation'
})
).toBeVisible()
// Expect the title to be used as fallback for the template categories
await expect(comfyPage.page.getByLabel('FALLBACK CATEGORY')).toBeVisible()
})
test('template cards are dynamically sized and responsive', async ({
@@ -151,46 +181,33 @@ test.describe('Templates', () => {
}) => {
// Open templates dialog
await comfyPage.executeCommand('Comfy.BrowseTemplates')
await expect(comfyPage.templates.content).toBeVisible()
await comfyPage.templates.content.waitFor({ state: 'visible' })
// Wait for at least one template card to appear
await expect(comfyPage.page.locator('.template-card').first()).toBeVisible({
timeout: 5000
})
const templateGrid = comfyPage.page.locator(
'[data-testid="template-workflows-content"]'
)
const nav = comfyPage.page
.locator('header')
.filter({ hasText: 'Templates' })
// Take snapshot of the template grid
const templateGrid = comfyPage.templates.content.locator('.grid').first()
const cardCount = await comfyPage.page
.locator('[data-testid^="template-workflow-"]')
.count()
expect(cardCount).toBeGreaterThan(0)
await expect(templateGrid).toBeVisible()
await expect(templateGrid).toHaveScreenshot('template-grid-desktop.png')
await expect(nav).toBeVisible() // Nav should be visible at desktop size
// Check cards at mobile viewport size
await comfyPage.page.setViewportSize({ width: 640, height: 800 })
const mobileSize = { width: 640, height: 800 }
await comfyPage.page.setViewportSize(mobileSize)
expect(cardCount).toBeGreaterThan(0)
await expect(templateGrid).toBeVisible()
await expect(templateGrid).toHaveScreenshot('template-grid-mobile.png')
await expect(nav).not.toBeVisible() // Nav should collapse at mobile size
// Check cards at tablet size
await comfyPage.page.setViewportSize({ width: 1024, height: 800 })
const tabletSize = { width: 1024, height: 800 }
await comfyPage.page.setViewportSize(tabletSize)
expect(cardCount).toBeGreaterThan(0)
await expect(templateGrid).toBeVisible()
await expect(templateGrid).toHaveScreenshot('template-grid-tablet.png')
})
test('hover effects work on template cards', async ({ comfyPage }) => {
// Open templates dialog
await comfyPage.executeCommand('Comfy.BrowseTemplates')
await expect(comfyPage.templates.content).toBeVisible()
// Get a template card
const firstCard = comfyPage.page.locator('.template-card').first()
await expect(firstCard).toBeVisible({ timeout: 5000 })
// Take snapshot before hover
await expect(firstCard).toHaveScreenshot('template-card-before-hover.png')
// Hover over the card
await firstCard.hover()
// Take snapshot after hover to verify hover effect
await expect(firstCard).toHaveScreenshot('template-card-after-hover.png')
await expect(nav).toBeVisible() // Nav should be visible at tablet size
})
test('template cards descriptions adjust height dynamically', async ({
@@ -257,21 +274,42 @@ test.describe('Templates', () => {
await comfyPage.executeCommand('Comfy.BrowseTemplates')
await expect(comfyPage.templates.content).toBeVisible()
// Verify cards are visible with varying content lengths
// Wait for cards to load
await expect(
comfyPage.page.getByText('This is a short description.')
).toBeVisible({ timeout: 5000 })
await expect(
comfyPage.page.getByText('This is a medium length description')
).toBeVisible({ timeout: 5000 })
await expect(
comfyPage.page.getByText('This is a much longer description')
comfyPage.page.locator(
'[data-testid="template-workflow-short-description"]'
)
).toBeVisible({ timeout: 5000 })
// Take snapshot of a grid with specific cards
const templateGrid = comfyPage.templates.content
.locator('.grid:has-text("Short Description")')
.first()
// Verify all three cards with different descriptions are visible
const shortDescCard = comfyPage.page.locator(
'[data-testid="template-workflow-short-description"]'
)
const mediumDescCard = comfyPage.page.locator(
'[data-testid="template-workflow-medium-description"]'
)
const longDescCard = comfyPage.page.locator(
'[data-testid="template-workflow-long-description"]'
)
await expect(shortDescCard).toBeVisible()
await expect(mediumDescCard).toBeVisible()
await expect(longDescCard).toBeVisible()
// Verify descriptions are visible and have line-clamp class
// The description is in a p tag with text-muted class
const shortDesc = shortDescCard.locator('p.text-muted.line-clamp-2')
const mediumDesc = mediumDescCard.locator('p.text-muted.line-clamp-2')
const longDesc = longDescCard.locator('p.text-muted.line-clamp-2')
await expect(shortDesc).toContainText('short description')
await expect(mediumDesc).toContainText('medium length description')
await expect(longDesc).toContainText('much longer description')
// Verify grid layout maintains consistency
const templateGrid = comfyPage.page.locator(
'[data-testid="template-workflows-content"]'
)
await expect(templateGrid).toBeVisible()
await expect(templateGrid).toHaveScreenshot(
'template-grid-varying-content.png'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Settings Search functionality', () => {
test.beforeEach(async ({ comfyPage }) => {
// Register test settings to verify hidden/deprecated filtering

View File

@@ -85,6 +85,7 @@ test.describe('Version Mismatch Warnings', () => {
test('should persist dismissed state across sessions', async ({
comfyPage
}) => {
test.setTimeout(30_000)
// Mock system_stats route to indicate that the installed version is always ahead of the required version
await comfyPage.page.route('**/system_stats**', async (route) => {
await route.fulfill({
@@ -106,6 +107,11 @@ test.describe('Version Mismatch Warnings', () => {
const dismissButton = warningToast.getByRole('button', { name: 'Close' })
await dismissButton.click()
// Wait for the dismissed state to be persisted
await comfyPage.page.waitForFunction(
() => !!localStorage.getItem('comfy.versionMismatch.dismissals')
)
// Reload the page, keeping local storage
await comfyPage.setup({ clearStorage: false })

View File

@@ -61,6 +61,19 @@ test.describe('NodeHeader', () => {
expect(titleAfterCancel).toBe('My Custom Sampler')
})
test('Double click node body does not trigger edit', async ({
comfyPage
}) => {
const loadCheckpointNode =
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const nodeBbox = await loadCheckpointNode.boundingBox()
if (!nodeBbox) throw new Error('Node not found')
await loadCheckpointNode.dblclick()
const editingTitleInput = comfyPage.page.getByTestId('node-title-input')
await expect(editingTitleInput).not.toBeVisible()
})
test('handles node collapsing', async ({ comfyPage }) => {
// Get the KSampler node from the default workflow
const nodes = await comfyPage.getNodeRefsByType('KSampler')

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Vue Nodes - Delete Key Interaction', () => {
test.beforeEach(async ({ comfyPage }) => {
// Enable Vue nodes rendering

View File

@@ -0,0 +1,33 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
test.describe('Vue Nodes Zoom', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should not capture drag while zooming with ctrl+shift+drag', async ({
comfyPage
}) => {
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const nodeBoundingBox = await checkpointNode.boundingBox()
if (!nodeBoundingBox) throw new Error('Node bounding box not available')
const nodeMidpointX = nodeBoundingBox.x + nodeBoundingBox.width / 2
const nodeMidpointY = nodeBoundingBox.y + nodeBoundingBox.height / 2
// Start the Ctrl+Shift drag-to-zoom on the canvas and continue dragging over
// the node. The node should not capture the drag while drag-zooming.
await comfyPage.page.keyboard.down('Control')
await comfyPage.page.keyboard.down('Shift')
await comfyPage.dragAndDrop(
{ x: 200, y: 300 },
{ x: nodeMidpointX, y: nodeMidpointY }
)
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in-ctrl-shift.png')
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,48 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Vue Nodes - LOD', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setup()
await comfyPage.loadWorkflow('default')
})
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
await comfyPage.vueNodes.waitForNodes()
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
expect(initialNodeCount).toBeGreaterThan(0)
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
const vueNodesContainer = comfyPage.vueNodes.nodes
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
const buttonsInNodes = vueNodesContainer.getByRole('button')
await expect(textboxesInNodes.first()).toBeVisible()
await expect(buttonsInNodes.first()).toBeVisible()
await comfyPage.zoom(120, 10)
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
await expect(textboxesInNodes.first()).toBeHidden()
await expect(buttonsInNodes.first()).toBeHidden()
await comfyPage.zoom(-120, 10)
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'vue-nodes-lod-inactive.png'
)
await expect(textboxesInNodes.first()).toBeVisible()
await expect(buttonsInNodes.first()).toBeVisible()
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -0,0 +1,51 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Vue Node Selection', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
const modifiers = [
{ key: 'Control', name: 'ctrl' },
{ key: 'Shift', name: 'shift' }
] as const
for (const { key: modifier, name } of modifiers) {
test(`should allow selecting multiple nodes with ${name}+click`, async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1)
await comfyPage.page.getByText('Empty Latent Image').click({
modifiers: [modifier]
})
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(2)
await comfyPage.page.getByText('KSampler').click({
modifiers: [modifier]
})
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(3)
})
test(`should allow de-selecting nodes with ${name}+click`, async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1)
await comfyPage.page.getByText('Load Checkpoint').click({
modifiers: [modifier]
})
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(0)
})
}
})

View File

@@ -0,0 +1,45 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
const BYPASS_HOTKEY = 'Control+b'
const BYPASS_CLASS = /before:bg-bypass\/60/
test.describe('Vue Node Bypass', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should allow toggling bypass on a selected node with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
})
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] })
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const ksamplerNode = comfyPage.vueNodes.getNodeByTitle('KSampler')
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
await expect(ksamplerNode).toHaveClass(BYPASS_CLASS)
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
await expect(ksamplerNode).not.toHaveClass(BYPASS_CLASS)
})
})

View File

@@ -0,0 +1,32 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
const ERROR_CLASS = /border-error/
test.describe('Vue Node Error', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should display error state when node is missing (node from workflow is not installed)', async ({
comfyPage
}) => {
await comfyPage.setup()
await comfyPage.loadWorkflow('missing/missing_nodes')
// Close missing nodes warning dialog
await comfyPage.page.getByRole('button', { name: 'Close' }).click()
await comfyPage.page.waitForSelector('.comfy-missing-nodes', {
state: 'hidden'
})
// Expect error state on missing unknown node
const unknownNode = comfyPage.page.locator('[data-node-id]').filter({
hasText: 'UNKNOWN NODE'
})
await expect(unknownNode).toHaveClass(ERROR_CLASS)
})
})

View File

@@ -0,0 +1,45 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
const MUTE_HOTKEY = 'Control+m'
const MUTE_CLASS = /opacity-50/
test.describe('Vue Node Mute', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should allow toggling mute on a selected node with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
await expect(checkpointNode).toHaveClass(MUTE_CLASS)
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
await expect(checkpointNode).not.toHaveClass(MUTE_CLASS)
})
test('should allow toggling mute on multiple selected nodes with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] })
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const ksamplerNode = comfyPage.vueNodes.getNodeByTitle('KSampler')
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
await expect(checkpointNode).toHaveClass(MUTE_CLASS)
await expect(ksamplerNode).toHaveClass(MUTE_CLASS)
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
await expect(checkpointNode).not.toHaveClass(MUTE_CLASS)
await expect(ksamplerNode).not.toHaveClass(MUTE_CLASS)
})
})

View File

@@ -0,0 +1,85 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../fixtures/ComfyPage'
const PIN_HOTKEY = 'p'
const PIN_INDICATOR = '[data-testid="node-pin-indicator"]'
test.describe('Vue Node Pin', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should allow toggling pin on a selected node with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.keyboard.press(PIN_HOTKEY)
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const pinIndicator = checkpointNode.locator(PIN_INDICATOR)
await expect(pinIndicator).toBeVisible()
await comfyPage.page.keyboard.press(PIN_HOTKEY)
await expect(pinIndicator).not.toBeVisible()
})
test('should allow toggling pin on multiple selected nodes with hotkey', async ({
comfyPage
}) => {
await comfyPage.page.getByText('Load Checkpoint').click()
await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] })
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
const ksamplerNode = comfyPage.vueNodes.getNodeByTitle('KSampler')
await comfyPage.page.keyboard.press(PIN_HOTKEY)
const pinIndicator1 = checkpointNode.locator(PIN_INDICATOR)
await expect(pinIndicator1).toBeVisible()
const pinIndicator2 = ksamplerNode.locator(PIN_INDICATOR)
await expect(pinIndicator2).toBeVisible()
await comfyPage.page.keyboard.press(PIN_HOTKEY)
await expect(pinIndicator1).not.toBeVisible()
await expect(pinIndicator2).not.toBeVisible()
})
test('should not allow dragging pinned nodes', async ({ comfyPage }) => {
const checkpointNodeHeader = comfyPage.page.getByText('Load Checkpoint')
await checkpointNodeHeader.click()
await comfyPage.page.keyboard.press(PIN_HOTKEY)
// Try to drag the node
const headerPos = await checkpointNodeHeader.boundingBox()
if (!headerPos) throw new Error('Failed to get header position')
await comfyPage.dragAndDrop(
{ x: headerPos.x, y: headerPos.y },
{ x: headerPos.x + 256, y: headerPos.y + 256 }
)
// Verify the node is not dragged (same position before and after click-and-drag)
const headerPosAfterDrag = await checkpointNodeHeader.boundingBox()
if (!headerPosAfterDrag)
throw new Error('Failed to get header position after drag')
expect(headerPosAfterDrag).toEqual(headerPos)
// Unpin the node with the hotkey
await checkpointNodeHeader.click()
await comfyPage.page.keyboard.press(PIN_HOTKEY)
// Try to drag the node again
await comfyPage.dragAndDrop(
{ x: headerPos.x, y: headerPos.y },
{ x: headerPos.x + 256, y: headerPos.y + 256 }
)
// Verify the node is dragged
const headerPosAfterDrag2 = await checkpointNodeHeader.boundingBox()
if (!headerPosAfterDrag2)
throw new Error('Failed to get header position after drag')
expect(headerPosAfterDrag2).not.toEqual(headerPos)
})
})

View File

@@ -0,0 +1,49 @@
import {
type ComfyPage,
comfyExpect as expect,
comfyPageFixture as test
} from '../../../../fixtures/ComfyPage'
test.describe('Vue Multiline String Widget', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
const getFirstClipNode = (comfyPage: ComfyPage) =>
comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode (Prompt)').first()
const getFirstMultilineStringWidget = (comfyPage: ComfyPage) =>
getFirstClipNode(comfyPage).getByRole('textbox', { name: 'text' })
test('should allow entering text', async ({ comfyPage }) => {
const textarea = getFirstMultilineStringWidget(comfyPage)
await textarea.fill('Hello World')
await expect(textarea).toHaveValue('Hello World')
await textarea.fill('Hello World 2')
await expect(textarea).toHaveValue('Hello World 2')
})
test('should support entering multiline content', async ({ comfyPage }) => {
const textarea = getFirstMultilineStringWidget(comfyPage)
const multilineValue = ['Line 1', 'Line 2', 'Line 3'].join('\n')
await textarea.fill(multilineValue)
await expect(textarea).toHaveValue(multilineValue)
})
test('should retain value after focus changes', async ({ comfyPage }) => {
const textarea = getFirstMultilineStringWidget(comfyPage)
await textarea.fill('Keep me around')
// Click another node
const loadCheckpointNode =
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
await loadCheckpointNode.click()
await getFirstClipNode(comfyPage).click()
await expect(textarea).toHaveValue('Keep me around')
})
})

View File

@@ -2,6 +2,10 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Combo text widget', () => {
test('Truncates text when resized', async ({ comfyPage }) => {
await comfyPage.resizeLoadCheckpointNode(0.2, 1)
@@ -318,6 +322,9 @@ test.describe('Animated image widget', () => {
test.describe('Load audio widget', () => {
test('Can load audio', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('widgets/load_audio_widget')
// Wait for the audio widget to be rendered in the DOM
await comfyPage.page.waitForSelector('.comfy-audio', { state: 'attached' })
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('load_audio_widget.png')
})
})

View File

@@ -3,7 +3,6 @@
"compilerOptions": {
/* Test files should not be compiled */
"noEmit": true,
// "strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true

17
build/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
/* Build scripts configuration */
"noEmit": true,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true
},
"include": [
"**/*.ts"
]
}

View File

@@ -110,7 +110,7 @@ pnpm build
For faster iteration during development, use watch mode:
```bash
npx vite build --watch
pnpm exec vite build --watch
```
Note: Watch mode provides faster rebuilds than full builds, but still no hot reload

View File

@@ -33,7 +33,13 @@ export default defineConfig([
},
parserOptions: {
parser: tseslint.parser,
projectService: true,
projectService: {
allowDefaultProject: [
'vite.config.mts',
'vite.electron.config.mts',
'vite.types.config.mts'
]
},
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
@@ -77,12 +83,25 @@ export default defineConfig([
'@typescript-eslint/prefer-as-const': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/no-empty-object-type': [
'error',
{
allowInterfaces: 'always'
}
],
'unused-imports/no-unused-imports': 'error',
'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
/* Toggle on to do additional until we can clean up existing violations.
'vue/no-unused-emit-declarations': 'error',
'vue/no-unused-properties': 'error',
'vue/no-unused-refs': 'error',
'vue/no-use-v-else-with-v-for': 'error',
'vue/no-useless-v-bind': 'error',
// */
'vue/one-component-per-file': 'off', // TODO: fix
'vue/require-default-prop': 'off', // TODO: fix -- this one is very worthwhile
// Restrict deprecated PrimeVue components
@@ -162,5 +181,31 @@ export default defineConfig([
{ disallowTypeAnnotations: false }
]
}
},
{
files: ['**/*.spec.ts'],
ignores: ['browser_tests/tests/**/*.spec.ts'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Program',
message: '.spec.ts files are only allowed under browser_tests/tests/'
}
]
}
},
{
files: ['browser_tests/tests/**/*.test.ts'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Program',
message:
'.test.ts files are not allowed in browser_tests/tests/; use .spec.ts instead'
}
]
}
}
])

View File

@@ -8,8 +8,8 @@
<link rel="stylesheet" type="text/css" href="user.css" />
<link rel="stylesheet" type="text/css" href="api/userdata/user.css" />
<!-- Fullscreen mode on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Fullscreen mode on mobile browsers -->
<meta name="mobile-web-app-capable" content="yes">
<!-- Status bar style (eg. black or transparent) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">

View File

@@ -1,15 +1,25 @@
import type { KnipConfig } from 'knip'
const config: KnipConfig = {
entry: [
'{build,scripts}/**/*.{js,ts}',
'src/assets/css/style.css',
'src/main.ts',
'src/scripts/ui/menu/index.ts',
'src/types/index.ts'
],
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}'],
ignoreBinaries: ['only-allow', 'openapi-typescript'],
workspaces: {
'.': {
entry: [
'{build,scripts}/**/*.{js,ts}',
'src/assets/css/style.css',
'src/main.ts',
'src/scripts/ui/menu/index.ts',
'src/types/index.ts'
],
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}']
},
'packages/tailwind-utils': {
project: ['src/**/*.{js,ts}']
},
'packages/design-system': {
entry: ['src/**/*.ts'],
project: ['src/**/*.{js,ts}', '*.{js,ts,mts}']
}
},
ignoreDependencies: [
// Weird importmap things
'@iconify/json',
@@ -25,9 +35,7 @@ const config: KnipConfig = {
'src/workbench/extensions/manager/types/generatedManagerTypes.ts',
'src/types/comfyRegistryTypes.ts',
// Used by a custom node (that should move off of this)
'src/scripts/ui/components/splitButton.ts',
// Staged for for use with subgraph widget promotion
'src/lib/litegraph/src/widgets/DisconnectedWidget.ts'
'src/scripts/ui/components/splitButton.ts'
],
compilers: {
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.28.0",
"version": "1.28.3",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -18,29 +18,30 @@
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache",
"format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --list-different",
"format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
"test:browser": "npx nx e2e",
"test:unit": "nx run test tests-ui/tests",
"test:all": "nx run test",
"test:browser": "pnpm exec nx e2e",
"test:component": "nx run test src/components/",
"test:litegraph": "vitest run --config vitest.litegraph.config.ts",
"preinstall": "npx only-allow pnpm",
"test:unit": "nx run test tests-ui/tests",
"preinstall": "pnpm dlx 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:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache",
"lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix",
"lint:no-cache": "eslint src",
"lint:fix:no-cache": "eslint src --fix",
"knip": "knip --cache",
"knip:no-cache": "knip",
"locale": "lobe-i18n locale",
"collect-i18n": "npx playwright test --config=playwright.i18n.config.ts",
"collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts",
"json-schema": "tsx scripts/generate-json-schema.ts",
"storybook": "nx storybook -p 6006",
"build-storybook": "storybook build"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@iconify-json/lucide": "^1.2.66",
"@iconify/tailwind": "^1.2.0",
"@intlify/eslint-plugin-vue-i18n": "^4.1.0",
"@lobehub/i18n-cli": "^1.25.1",
"@nx/eslint": "21.4.1",
@@ -94,6 +95,7 @@
"vite-plugin-html": "^3.2.2",
"vite-plugin-vue-devtools": "^7.7.6",
"vitest": "^3.2.4",
"vue-component-type-helpers": "^3.0.7",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.7",
"zip-dir": "^2.0.0",
@@ -103,6 +105,8 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/design-system": "workspace:*",
"@comfyorg/tailwind-utils": "workspace:*",
"@iconify/json": "^2.2.380",
"@primeuix/forms": "0.0.2",
"@primeuix/styled": "0.3.2",
@@ -120,13 +124,13 @@
"@tiptap/extension-table-row": "^2.10.4",
"@tiptap/starter-kit": "^2.10.4",
"@vueuse/core": "^11.0.0",
"@vueuse/integrations": "^13.9.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0",
"algoliasearch": "^5.21.0",
"axios": "^1.8.2",
"chart.js": "^4.5.0",
"clsx": "^2.1.1",
"dompurify": "^3.2.5",
"dotenv": "^16.4.5",
"es-toolkit": "^1.39.9",
@@ -144,7 +148,6 @@
"primevue": "^4.2.5",
"reka-ui": "^2.5.0",
"semver": "^7.7.2",
"tailwind-merge": "^3.3.1",
"three": "^0.170.0",
"tiptap-markdown": "^0.8.10",
"vue": "^3.5.13",

View File

@@ -0,0 +1,31 @@
{
"name": "@comfyorg/design-system",
"version": "1.0.0",
"description": "Shared design system for ComfyUI Frontend",
"type": "module",
"exports": {
"./tailwind-config": {
"import": "./tailwind.config.ts",
"types": "./tailwind.config.ts"
},
"./css/*": "./src/css/*"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"nx": {
"tags": [
"scope:shared",
"type:design"
]
},
"dependencies": {
"@iconify-json/lucide": "^1.1.178",
"@iconify/tailwind": "^1.1.3"
},
"devDependencies": {
"tailwindcss": "^3.4.17",
"typescript": "^5.4.5"
},
"packageManager": "pnpm@10.17.1"
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url'
const fileName = fileURLToPath(import.meta.url)
const dirName = dirname(fileName)
const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom')
const customIconsPath = join(dirName, 'icons')
// Iconify collection structure
interface IconifyIcon {

View File

@@ -51,7 +51,7 @@ ComfyUI supports three types of icons that can be used throughout the interface.
```vue
<template>
<!-- Your custom SVG icons from src/assets/icons/custom/ -->
<!-- Your custom SVG icons from packages/design-system/src/icons/ -->
<i-comfy:workflow />
<i-comfy:node-tree />
<i-comfy:my-custom-icon class="text-xl" />
@@ -128,10 +128,10 @@ ComfyUI supports three types of icons that can be used throughout the interface.
### 1. Add Your SVG
Place your SVG file in `src/assets/icons/custom/`:
Place your SVG file in `packages/design-system/src/icons/`:
```
src/assets/icons/custom/
packages/design-system/src/icons/
├── workflow-duplicate.svg
├── node-preview.svg
└── your-icon.svg
@@ -249,8 +249,8 @@ Icons are automatically imported using `unplugin-icons` - no manual imports need
The icon system has two layers:
1. **Build-time Processing** (`build/customIconCollection.ts`):
- Scans `src/assets/icons/custom/` for SVG files
1. **Build-time Processing** (`packages/design-system/src/iconCollection.ts`):
- Scans `packages/design-system/src/icons/` for SVG files
- Validates SVG content and structure
- Creates Iconify collection for Tailwind CSS
- Provides error handling for malformed files
@@ -273,7 +273,7 @@ export const iconCollection: IconifyCollection = {
Icons({
compiler: 'vue3',
customCollections: {
comfy: FileSystemIconLoader('src/assets/icons/custom')
comfy: FileSystemIconLoader('packages/design-system/src/icons')
}
})
```

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 938 B

After

Width:  |  Height:  |  Size: 938 B

View File

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 411 B

View File

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 830 B

View File

Before

Width:  |  Height:  |  Size: 857 B

After

Width:  |  Height:  |  Size: 857 B

View File

@@ -0,0 +1,26 @@
import lucide from '@iconify-json/lucide/icons.json' with { type: 'json' }
import { addDynamicIconSelectors } from '@iconify/tailwind'
import { iconCollection } from './src/iconCollection'
export default {
content: [],
safelist: [
'icon-[lucide--folder]',
'icon-[lucide--package]',
'icon-[lucide--image]',
'icon-[lucide--video]',
'icon-[lucide--box]',
'icon-[lucide--audio-waveform]',
'icon-[lucide--message-circle]'
],
plugins: [
addDynamicIconSelectors({
iconSets: {
comfy: iconCollection,
lucide
},
prefix: 'icon'
})
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,31 @@
# @comfyorg/tailwind-utils
Shared Tailwind CSS utility functions for the ComfyUI Frontend monorepo.
## Usage
The `cn` function combines `clsx` and `tailwind-merge` to handle conditional classes and resolve Tailwind conflicts.
```typescript
import { cn } from '@comfyorg/tailwind-utils'
// Use with conditional classes (object)
<div :class="cn('transition-opacity', { 'opacity-75': !isHovered })" />
// Use with conditional classes (ternary)
<button
:class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-gray-500')"
/>
```
## Installation
This package is part of the ComfyUI Frontend monorepo and is automatically available to all workspace packages.
```json
{
"dependencies": {
"@comfyorg/tailwind-utils": "workspace:*"
}
}
```

View File

@@ -0,0 +1,30 @@
{
"name": "@comfyorg/tailwind-utils",
"version": "1.0.0",
"type": "module",
"description": "Shared Tailwind CSS utilities for ComfyUI Frontend",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
}
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"nx": {
"tags": [
"scope:shared",
"type:util"
]
},
"dependencies": {
"clsx": "^2.1.1",
"tailwind-merge": "^2.2.0"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}

View File

@@ -0,0 +1,8 @@
import clsx, { type ClassArray } from 'clsx'
import { twMerge } from 'tailwind-merge'
export type { ClassValue } from 'clsx'
export function cn(...inputs: ClassArray) {
return twMerge(clsx(inputs))
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*"]
}

View File

@@ -7,6 +7,7 @@ export default defineConfig({
headless: true
},
reporter: 'list',
workers: 1,
timeout: 60000,
testMatch: /collect-i18n-.*\.ts/
})

356
pnpm-lock.yaml generated
View File

@@ -17,6 +17,12 @@ importers:
'@comfyorg/comfyui-electron-types':
specifier: 0.4.73-0
version: 0.4.73-0
'@comfyorg/design-system':
specifier: workspace:*
version: link:packages/design-system
'@comfyorg/tailwind-utils':
specifier: workspace:*
version: link:packages/tailwind-utils
'@iconify/json':
specifier: ^2.2.380
version: 2.2.380
@@ -68,6 +74,9 @@ importers:
'@vueuse/core':
specifier: ^11.0.0
version: 11.0.0(vue@3.5.13(typescript@5.9.2))
'@vueuse/integrations':
specifier: ^13.9.0
version: 13.9.0(axios@1.11.0)(fuse.js@7.0.0)(vue@3.5.13(typescript@5.9.2))
'@xterm/addon-fit':
specifier: ^0.10.0
version: 0.10.0(@xterm/xterm@5.5.0)
@@ -86,9 +95,6 @@ importers:
chart.js:
specifier: ^4.5.0
version: 4.5.0
clsx:
specifier: ^2.1.1
version: 2.1.1
dompurify:
specifier: ^3.2.5
version: 3.2.5
@@ -140,9 +146,6 @@ importers:
semver:
specifier: ^7.7.2
version: 7.7.2
tailwind-merge:
specifier: ^3.3.1
version: 3.3.1
three:
specifier: ^0.170.0
version: 0.170.0
@@ -174,12 +177,6 @@ importers:
'@eslint/js':
specifier: ^9.35.0
version: 9.35.0
'@iconify-json/lucide':
specifier: ^1.2.66
version: 1.2.66
'@iconify/tailwind':
specifier: ^1.2.0
version: 1.2.0
'@intlify/eslint-plugin-vue-i18n':
specifier: ^4.1.0
version: 4.1.0(eslint@9.35.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0)(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2)))(yaml-eslint-parser@1.3.0)
@@ -339,6 +336,9 @@ importers:
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2)
vue-component-type-helpers:
specifier: ^3.0.7
version: 3.0.7
vue-eslint-parser:
specifier: ^10.2.0
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
@@ -352,6 +352,35 @@ importers:
specifier: ^3.24.1
version: 3.24.1(zod@3.24.1)
packages/design-system:
dependencies:
'@iconify-json/lucide':
specifier: ^1.1.178
version: 1.2.66
'@iconify/tailwind':
specifier: ^1.1.3
version: 1.2.0
devDependencies:
tailwindcss:
specifier: ^3.4.17
version: 3.4.17
typescript:
specifier: ^5.4.5
version: 5.9.2
packages/tailwind-utils:
dependencies:
clsx:
specifier: ^2.1.1
version: 2.1.1
tailwind-merge:
specifier: ^2.2.0
version: 2.6.0
devDependencies:
typescript:
specifier: ^5.4.5
version: 5.9.2
packages:
'@adobe/css-tools@4.4.3':
@@ -2892,18 +2921,73 @@ packages:
'@vueuse/core@12.8.2':
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
'@vueuse/core@13.9.0':
resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
peerDependencies:
vue: ^3.5.0
'@vueuse/integrations@13.9.0':
resolution: {integrity: sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==}
peerDependencies:
async-validator: ^4
axios: ^1
change-case: ^5
drauu: ^0.4
focus-trap: ^7
fuse.js: ^7
idb-keyval: ^6
jwt-decode: ^4
nprogress: ^0.2
qrcode: ^1.5
sortablejs: ^1
universal-cookie: ^7 || ^8
vue: ^3.5.0
peerDependenciesMeta:
async-validator:
optional: true
axios:
optional: true
change-case:
optional: true
drauu:
optional: true
focus-trap:
optional: true
fuse.js:
optional: true
idb-keyval:
optional: true
jwt-decode:
optional: true
nprogress:
optional: true
qrcode:
optional: true
sortablejs:
optional: true
universal-cookie:
optional: true
'@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/metadata@13.9.0':
resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
'@vueuse/shared@11.0.0':
resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==}
'@vueuse/shared@12.8.2':
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
'@vueuse/shared@13.9.0':
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
peerDependencies:
vue: ^3.5.0
'@webgpu/types@0.1.51':
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
@@ -3045,10 +3129,16 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@@ -3224,6 +3314,10 @@ packages:
camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
camelcase@8.0.0:
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
engines: {node: '>=16'}
@@ -3353,6 +3447,10 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
@@ -3558,6 +3656,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
@@ -3565,6 +3666,9 @@ packages:
resolution: {integrity: sha512-7SCDfnQtBObcngVXNPZcnxGxqqPTK4UqeXeKAch+RGH5qpqadWbV9FmN71x9Bb4tTs0TNFb4FT/4Kz4P4Cjqcw==}
engines: {node: '>=6.0.0'}
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
doctypes@1.1.0:
resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==}
@@ -4453,6 +4557,10 @@ packages:
resolution: {integrity: sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
@@ -4676,6 +4784,10 @@ packages:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
engines: {node: '>=14'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -5038,6 +5150,9 @@ packages:
resolution: {integrity: sha512-SsI/exkodHsh+ofCV7An2PZWRaJC7eFVl7gtHQlMWFEDmWtb7cELr/GK32Nhe/6dZQhbr81o+Moswx9aXN3RRg==}
engines: {node: '>=18.2.0'}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nanoid@3.3.8:
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -5129,6 +5244,10 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
object-keys@1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
@@ -5304,6 +5423,10 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
pinia@2.2.2:
resolution: {integrity: sha512-ja2XqFWZC36mupU4z1ZzxeTApV7DOw44cV4dhQ9sGwun+N89v/XP7+j7q6TanS1u1tdbK4r+1BUx7heMaIdagA==}
peerDependencies:
@@ -5316,6 +5439,10 @@ packages:
typescript:
optional: true
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -5332,10 +5459,47 @@ packages:
engines: {node: '>=18'}
hasBin: true
postcss-import@15.1.0:
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
postcss-js@4.1.0:
resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
postcss-load-config@4.0.2:
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
postcss-nested@6.2.0:
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
postcss-selector-parser@6.1.0:
resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==}
engines: {node: '>=4'}
postcss-selector-parser@6.1.2:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.1:
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -5532,6 +5696,9 @@ packages:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
@@ -5853,6 +6020,11 @@ packages:
subscribable-things@2.1.53:
resolution: {integrity: sha512-zWvN9F/eYQWDKszXl4NXkyqPXvMDZDmXfcHiM5C5WQZTTY2OK+2TZeDlA9oio69FEPqPu9T6yeEcAhQ2uRmnaw==}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
superjson@2.2.2:
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
engines: {node: '>=16'}
@@ -5885,14 +6057,19 @@ packages:
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
engines: {node: ^14.18.0 || >=16.0.0}
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
tailwindcss-primeui@0.6.1:
resolution: {integrity: sha512-T69Rylcrmnt8zy9ik+qZvsLuRIrS9/k6rYJSIgZ1trnbEzGDDQSCIdmfyZknevqiHwpSJHSmQ9XT2C+S/hJY4A==}
peerDependencies:
tailwindcss: '>=3.1.0'
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
hasBin: true
tailwindcss@4.1.12:
resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==}
@@ -5917,6 +6094,13 @@ packages:
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
engines: {node: '>=18'}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
three@0.170.0:
resolution: {integrity: sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==}
@@ -6003,6 +6187,9 @@ packages:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
ts-map@1.0.3:
resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
@@ -6320,6 +6507,9 @@ packages:
vue-component-type-helpers@3.0.7:
resolution: {integrity: sha512-TvyUcFXmjZcXUvU+r1MOyn4/vv4iF+tPwg5Ig33l/FJ3myZkxeQpzzQMLMFWcQAjr6Xs7BRwVy/TwbmNZUA/4w==}
vue-component-type-helpers@3.0.8:
resolution: {integrity: sha512-WyR30Eq15Y/+odrUUMax6FmPbZwAp/HnC7qgR1r3lVFAcqwQ4wUoV79Mbh4SxDy3NiqDa+G4TOKD5xXSgBHo5A==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
@@ -8876,7 +9066,7 @@ snapshots:
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
type-fest: 2.19.0
vue: 3.5.13(typescript@5.9.2)
vue-component-type-helpers: 3.0.7
vue-component-type-helpers: 3.0.8
'@swc/helpers@0.5.17':
dependencies:
@@ -9659,10 +9849,28 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vueuse/core@13.9.0(vue@3.5.13(typescript@5.9.2))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 13.9.0
'@vueuse/shared': 13.9.0(vue@3.5.13(typescript@5.9.2))
vue: 3.5.13(typescript@5.9.2)
'@vueuse/integrations@13.9.0(axios@1.11.0)(fuse.js@7.0.0)(vue@3.5.13(typescript@5.9.2))':
dependencies:
'@vueuse/core': 13.9.0(vue@3.5.13(typescript@5.9.2))
'@vueuse/shared': 13.9.0(vue@3.5.13(typescript@5.9.2))
vue: 3.5.13(typescript@5.9.2)
optionalDependencies:
axios: 1.11.0
fuse.js: 7.0.0
'@vueuse/metadata@11.0.0': {}
'@vueuse/metadata@12.8.2': {}
'@vueuse/metadata@13.9.0': {}
'@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))
@@ -9676,6 +9884,10 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vueuse/shared@13.9.0(vue@3.5.13(typescript@5.9.2))':
dependencies:
vue: 3.5.13(typescript@5.9.2)
'@webgpu/types@0.1.51': {}
'@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)':
@@ -9809,11 +10021,15 @@ snapshots:
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
arg@5.0.2: {}
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
@@ -10019,6 +10235,8 @@ snapshots:
pascal-case: 3.1.2
tslib: 2.8.1
camelcase-css@2.0.1: {}
camelcase@8.0.0: {}
caniuse-lite@1.0.30001737: {}
@@ -10132,6 +10350,8 @@ snapshots:
commander@2.20.3: {}
commander@4.1.1: {}
commander@8.3.0: {}
compare-versions@6.1.1: {}
@@ -10314,6 +10534,8 @@ snapshots:
dependencies:
dequal: 2.0.3
didyoumean@1.2.2: {}
diff-match-patch@1.0.5: {}
dirty-json@0.9.2:
@@ -10322,6 +10544,8 @@ snapshots:
unescape-js: 1.1.4
utf8: 3.0.0
dlv@1.1.3: {}
doctypes@1.1.0: {}
dom-accessibility-api@0.5.16: {}
@@ -11271,6 +11495,8 @@ snapshots:
chalk: 4.1.2
pretty-format: 30.0.5
jiti@1.21.7: {}
jiti@2.4.2: {}
jiti@2.5.1: {}
@@ -11492,6 +11718,8 @@ snapshots:
lilconfig@3.1.2: {}
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
lines-and-columns@2.0.3: {}
@@ -12048,6 +12276,12 @@ snapshots:
'@babel/runtime': 7.28.4
tslib: 2.8.1
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
nanoid@3.3.8: {}
nanoid@5.1.5: {}
@@ -12161,6 +12395,8 @@ snapshots:
object-assign@4.1.1: {}
object-hash@3.0.0: {}
object-keys@1.1.1: {}
ohash@2.0.11: {}
@@ -12351,6 +12587,8 @@ snapshots:
pidtree@0.6.0: {}
pify@2.3.0: {}
pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)):
dependencies:
'@vue/devtools-api': 6.6.3
@@ -12359,6 +12597,8 @@ snapshots:
optionalDependencies:
typescript: 5.9.2
pirates@4.0.7: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@@ -12379,11 +12619,42 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
postcss-import@15.1.0(postcss@8.5.1):
dependencies:
postcss: 8.5.1
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.10
postcss-js@4.1.0(postcss@8.5.1):
dependencies:
camelcase-css: 2.0.1
postcss: 8.5.1
postcss-load-config@4.0.2(postcss@8.5.1):
dependencies:
lilconfig: 3.1.3
yaml: 2.8.1
optionalDependencies:
postcss: 8.5.1
postcss-nested@6.2.0(postcss@8.5.1):
dependencies:
postcss: 8.5.1
postcss-selector-parser: 6.1.2
postcss-selector-parser@6.1.0:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss-value-parser@4.2.0: {}
postcss@8.5.1:
dependencies:
nanoid: 3.3.8
@@ -12659,6 +12930,10 @@ snapshots:
react@19.1.1: {}
read-cache@1.0.0:
dependencies:
pify: 2.3.0
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
@@ -13041,6 +13316,16 @@ snapshots:
rxjs-interop: 2.0.0
tslib: 2.8.1
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
commander: 4.1.1
glob: 10.4.5
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
ts-interface-checker: 0.1.13
superjson@2.2.2:
dependencies:
copy-anything: 3.0.5
@@ -13072,12 +13357,39 @@ snapshots:
dependencies:
'@pkgr/core': 0.2.9
tailwind-merge@3.3.1: {}
tailwind-merge@2.6.0: {}
tailwindcss-primeui@0.6.1(tailwindcss@4.1.12):
dependencies:
tailwindcss: 4.1.12
tailwindcss@3.4.17:
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.6.0
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.3
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.7
lilconfig: 3.1.3
micromatch: 4.0.8
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
postcss: 8.5.1
postcss-import: 15.1.0(postcss@8.5.1)
postcss-js: 4.1.0(postcss@8.5.1)
postcss-load-config: 4.0.2(postcss@8.5.1)
postcss-nested: 6.2.0(postcss@8.5.1)
postcss-selector-parser: 6.1.2
resolve: 1.22.10
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
tailwindcss@4.1.12: {}
tapable@2.2.3: {}
@@ -13112,6 +13424,14 @@ snapshots:
glob: 10.4.5
minimatch: 9.0.5
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
three@0.170.0: {}
tiny-invariant@1.3.3: {}
@@ -13177,6 +13497,8 @@ snapshots:
ts-dedent@2.2.0: {}
ts-interface-checker@0.1.13: {}
ts-map@1.0.3: {}
tsconfig-paths@4.2.0:
@@ -13542,6 +13864,8 @@ snapshots:
vue-component-type-helpers@3.0.7: {}
vue-component-type-helpers@3.0.8: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
dependencies:
vue: 3.5.13(typescript@5.9.2)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,33 +1,48 @@
import * as fs from 'fs'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import type { ComfyNodeDef } from '../src/schemas/nodeDefSchema'
import type { ComfyApi } from '../src/scripts/api'
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import type { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import { normalizeI18nKey } from '../src/utils/formatUtil'
const localePath = './src/locales/en/main.json'
const nodeDefsPath = './src/locales/en/nodeDefs.json'
interface WidgetInfo {
name?: string
label?: string
}
interface WidgetLabels {
[key: string]: Record<string, { name: string }>
}
test('collect-i18n-node-defs', async ({ comfyPage }) => {
// Mock view route
comfyPage.page.route('**/view**', async (route) => {
await comfyPage.page.route('**/view**', async (route) => {
await route.fulfill({
body: JSON.stringify({})
})
})
const nodeDefs: ComfyNodeDefImpl[] = (
Object.values(
await comfyPage.page.evaluate(async () => {
const api = window['app'].api as ComfyApi
return await api.getNodeDefs()
})
) as ComfyNodeDef[]
// Note: Don't mock the object_info API endpoint - let it hit the actual backend
const nodeDefs: ComfyNodeDefImpl[] = await comfyPage.page.evaluate(
async () => {
// @ts-expect-error - app is dynamically added to window
const api = window['app'].api
const rawNodeDefs = await api.getNodeDefs()
const { ComfyNodeDefImpl } = await import('../src/stores/nodeDefStore')
return (
Object.values(rawNodeDefs)
// Ignore DevTools nodes (used for internal testing)
.filter((def: ComfyNodeDef) => !def.name.startsWith('DevTools'))
.map((def: ComfyNodeDef) => new ComfyNodeDefImpl(def))
)
}
)
// Ignore DevTools nodes (used for internal testing)
.filter((def) => !def.name.startsWith('DevTools'))
.map((def) => new ComfyNodeDefImpl(def))
console.log(`Collected ${nodeDefs.length} node definitions`)
@@ -52,7 +67,7 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
)
async function extractWidgetLabels() {
const nodeLabels = {}
const nodeLabels: WidgetLabels = {}
for (const nodeDef of nodeDefs) {
const inputNames = Object.values(nodeDef.inputs).map(
@@ -65,12 +80,15 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
const widgetsMappings = await comfyPage.page.evaluate(
(args) => {
const [nodeName, displayName, inputNames] = args
// @ts-expect-error - LiteGraph is dynamically added to window
const node = window['LiteGraph'].createNode(nodeName, displayName)
if (!node.widgets?.length) return {}
return Object.fromEntries(
node.widgets
.filter((w) => w?.name && !inputNames.includes(w.name))
.map((w) => [w.name, w.label])
.filter(
(w: WidgetInfo) => w?.name && !inputNames.includes(w.name)
)
.map((w: WidgetInfo) => [w.name, w.label])
)
},
[nodeDef.name, nodeDef.display_name, inputNames]

View File

@@ -72,7 +72,7 @@ function capture(srcLocaleDir: string, tempBaseDir: string) {
const relativePath = file.replace(srcLocaleDir, '')
const targetPath = join(tempBaseDir, relativePath)
ensureDir(dirname(targetPath))
writeFileSync(targetPath, readFileSync(file))
writeFileSync(targetPath, readFileSync(file, 'utf8'))
}
console.log('Captured current locale files to temp/base/')
}

14
scripts/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
/* Script files configuration */
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true
},
"include": [
"**/*.ts"
]
}

View File

@@ -17,8 +17,8 @@ import { computed, onMounted } from 'vue'
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
import config from '@/config'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
import { useConflictDetection } from './composables/useConflictDetection'
import { electronAPI, isElectron } from './utils/envUtil'
const workspaceStore = useWorkspaceStore()

Some files were not shown because too many files have changed in this diff Show More