Compare commits

...

150 Commits

Author SHA1 Message Date
GitHub Action
5a71450b85 [automated] Apply ESLint and Prettier fixes 2025-12-01 22:10:56 +00:00
coderabbitai[bot]
6031e6ccf2 📝 Add docstrings to drjkl/byom-2
Docstrings generation was requested by @guill.

* https://github.com/Comfy-Org/ComfyUI_frontend/pull/6969#issuecomment-3583609253

The following files were modified:

* `src/components/dialog/confirm/confirmDialog.ts`
* `src/composables/useFeatureFlags.ts`
* `src/platform/assets/services/assetService.ts`
* `src/services/dialogService.ts`
* `src/stores/dialogStore.ts`
2025-12-01 22:08:29 +00:00
DrJKL
4417b0d907 Update flag name 2025-12-01 13:40:51 -08:00
DrJKL
ff988cd6f1 cleanup: More consistent asset name 2025-12-01 13:39:14 -08:00
DrJKL
38cc93b8f6 Grammar is hard 2025-12-01 13:35:24 -08:00
DrJKL
23d8ccd394 UX: Gate the buttons behind a feature flag 2025-12-01 13:31:42 -08:00
DrJKL
23d01f0b34 UX: Handle failed update 2025-12-01 13:24:24 -08:00
DrJKL
64a7955eca UX: Add progress and confirmation to deletion 2025-12-01 13:09:08 -08:00
DrJKL
a5bd37ef56 Properly resolve MaybeRef 2025-12-01 12:00:00 -08:00
Alexander Brown
ca539a6fd0 Update src/platform/assets/components/AssetBadgeGroup.vue
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-01 11:50:44 -08:00
DrJKL
585e47f2df We should remove that reexport and standardize the import 2025-12-01 11:48:17 -08:00
DrJKL
aa13e4c385 i18n: internationalize deletion confirmation 2025-12-01 11:48:17 -08:00
DrJKL
57ed0afde6 nits: Typo and unused ref 2025-12-01 11:48:17 -08:00
DrJKL
3a18d27b9d WIP: Real API calls 2025-12-01 11:48:16 -08:00
DrJKL
f9e8dfdbe7 style: Hide details button for now 2025-12-01 11:48:16 -08:00
DrJKL
ed68b085cc style: Make sure extra properties hug the bottom of the card. 2025-12-01 11:48:15 -08:00
DrJKL
bb9475fed6 Cleanup: Remove dark-theme variants in buttonTypes. Really need to migrate this to CVA and setup the right types. 2025-12-01 11:48:14 -08:00
DrJKL
ba43494d32 WIP: Deleting assets, UI only, also not wired up 2025-12-01 11:48:14 -08:00
DrJKL
df82698f1e type: Add component prop typesafety to dialogs 2025-12-01 11:48:13 -08:00
DrJKL
d4b993b16d style: Fix card padding 2025-12-01 11:48:13 -08:00
DrJKL
04ab767649 WIP: Renaming Assets, UI, not hooked up to backend. 2025-12-01 11:48:12 -08:00
DrJKL
0b7d0f1d35 style: non-functional buttons and menu 2025-12-01 11:48:11 -08:00
DrJKL
0dfe36f1f8 style: Align card styles with Designs 2025-12-01 11:48:08 -08:00
Alexander Piskun
2c437acff6 feat(api-nodes-pricing): add prices for Kling O1 video model (#7077)
## Summary

Add price badges for the upcoming API nodes.

## Screenshots (if applicable)

<img width="1242" height="1094" alt="Screenshot From 2025-12-01
14-03-22"
src="https://github.com/user-attachments/assets/8f7909ea-f629-4dde-a075-ca31a9fff2ac"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7077-feat-api-nodes-pricing-add-prices-for-Kling-O1-video-model-2bc6d73d36508152843ff96196317ff8)
by [Unito](https://www.unito.io)
2025-12-01 11:02:34 -08:00
Comfy Org PR Bot
b97b21add0 1.34.3 (#7074)
Patch version increment to 1.34.3

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7074-1-34-3-2bc6d73d365081e9be00e6c1438142fd)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-30 23:48:38 -07:00
Christian Byrne
dfe098897a [fix] Prevent drag activation during Vue node resize (#7064)
## Summary
- Add isResizingVueNodes flag to layoutStore
- Set flag in useNodeResize during resize operation
- Check flag in useNodePointerInteractions to skip drag activation

Fixes a bug where resizing a Vue node after selecting it would
incorrectly trigger drag logic, causing the node to teleport.


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7064-fix-Prevent-drag-activation-during-Vue-node-resize-2bb6d73d365081a2ad0ad4c7be76afee)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <DrJKL0424@gmail.com>
2025-11-30 20:23:30 -08:00
Christian Byrne
d76c59cb14 [refactor] Simplify Vue node resize to bottom-right corner only (#7063)
## Summary

Temporarily simplifies the resize logic to only work on bottom right
corner and eliminates edge cases where corner resizing caused position
drift issues.

- Remove multi-corner resize handles in favor of bottom-right only
- Delete resizeMath.ts and its tests (no longer needed)
- Simplify useNodeResize to only handle bottom-right resize
- Remove position tracking from resize callback

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-30 18:27:37 -08:00
Christian Byrne
c298d8a870 [fix] Weekly release workflow adjustments (#7060)
Follow-up adjustments to the weekly ComfyUI release automation workflow.

## Changes

1. **Rename workflow to follow conventions**
   - File: `weekly-comfyui-release.yaml` → `release-weekly-comfyui.yaml`
   - Name: "Weekly ComfyUI Release" → "Release: Weekly ComfyUI"
   - Matches pattern of other `release-*` workflows

2. **Sync fork with upstream before creating PR**
   - Fetches latest upstream/master before making changes
   - Ensures PR only shows requirements.txt diff, not stale fork commits
- Does not modify fork's master branch (only pushes automation branch)

## Testing

After merge, can test via manual workflow dispatch in Actions tab.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7060-fix-Weekly-release-workflow-adjustments-2bb6d73d365081008436d1b9e5f7dd65)
by [Unito](https://www.unito.io)
2025-11-30 18:27:04 -08:00
niknah
b50b34ac95 Expose LGraphNode.getSlotPosition (#7042)
Before node v2, you could use getInputPos, getOutputPos. In node v2,
that is not possible.

## Summary

Want to get the position of the output / inputs on the graph like before
node v2.

## Changes

- **What**: <!-- Core functionality added/modified --> Added
LGraphNode.getSlotPosition

## Review Focus


## Screenshots (if applicable)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7042-Expose-LGraphNode-getSlotPosition-2ba6d73d36508195a0fed2d1fe2b64f6)
by [Unito](https://www.unito.io)
2025-11-30 12:55:16 -07:00
Christian Byrne
7a6cc39c39 [feat] Add weekly ComfyUI release automation (#6877)
Adds scheduled workflow to bump ComfyUI frontend RC releases every
Monday at noon PST.

## Implementation

- **Resolver script** (`scripts/cicd/resolve-comfyui-release.ts`):
Checks ComfyUI `requirements.txt` and determines next minor version,
compares branch commits to latest patch tag
- **Workflow** (`.github/workflows/weekly-comfyui-release.yaml`): 
  - Scheduled for Monday 20:00 UTC (noon PST)
  - Manual dispatch supported for testing/off-cycle runs
- Three jobs: resolve version → trigger release if needed → create
ComfyUI PR
- Reuses existing `release-version-bump.yaml` workflow
- Creates draft PR from fork to `comfyanonymous/ComfyUI` with updated
`requirements.txt`
- Includes error handling and validation for all steps
- Force pushes to same branch weekly to maintain single open PR

## Testing

- Resolver script tested locally: correctly identified v1.28.8 → v1.29.4
with release needed
- yamllint passes
- knip passes with `gh` binary added to ignore list

## Follow-up

Can test via workflow_dispatch once merged, or in this PR if we enable
Actions on branch.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6877-feat-Add-weekly-ComfyUI-release-automation-2b46d73d36508154aa05c783c6942d9a)
by [Unito](https://www.unito.io)
2025-11-30 12:45:47 -07:00
Christian Byrne
2b751200be test: add setting to ignore version compatibility toast warnings in e2e tests (#7004)
There's a warning toast shown if the frontend is considered out-of-date
(relative to the version in the requirements.txt of
comfyanonymous/ComfyUI). As a result, e2e tests run on older release
branches (e.g., when backporting or hotfixing) can sometimes trigger the
warning which obviously causes visual regression tests to fail. This PR
adds a hidden setting to disable the warning and sets it to `true` in
the e2e test fixtures.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7004-test-add-setting-to-ignore-version-compatibility-toast-warnings-in-e2e-tests-2b86d73d3650812d9e07f54a0c86b996)
by [Unito](https://www.unito.io)
2025-11-29 19:56:19 -07:00
Alexander Brown
5b03d3fcbc Minor/Lint: Fix two warnings (#7045)
## Summary

Unnecessary import of a compiler macro and importing the default instead
of the named export.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7045-Minor-Lint-Fix-two-warnings-2bb6d73d365081758369d14bafcd7aa0)
by [Unito](https://www.unito.io)
2025-11-29 19:15:06 -07:00
Christian Byrne
1caf3fdb0c remove temporary markdown file (#7048)
Deletes a file accidentally committed when rebasing and doing `git add
.`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7048-remove-temporary-markdown-file-2bb6d73d36508139b95ef3c7e4ab0798)
by [Unito](https://www.unito.io)
2025-11-29 19:13:37 -07:00
Christian Byrne
f39faf6b8e mark vue nodes menu toggle with beta tag (#7047)
## Summary

Adds a "Beta" tag next to the Nodes 2.0 menu item toggle to better
indicate to users, especially new users, that the feature is in beta.

<img width="752" height="918" alt="Selection_2477"
src="https://github.com/user-attachments/assets/cf341b2b-1af6-4259-8449-bf157989b7b3"
/>

<img width="433" height="880" alt="Selection_2476"
src="https://github.com/user-attachments/assets/a0f658bf-1904-4d79-9ca7-c81c458adc54"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7047-mark-vue-nodes-menu-toggle-with-beta-tag-2bb6d73d3650815e8a9aceaa9250c02e)
by [Unito](https://www.unito.io)
2025-11-29 18:33:57 -07:00
Christian Byrne
3ee921119d fix: loader node widget value shows placeholder instead of filename on cloud (#7005)
Fixes issue on cloud where the Loader node dropdowns showed placeholder
values (after refresh) in the widget UI despite a value being loaded.

Cloud loader dropdowns hydrate via `useAssetWidgetData(nodeType)`, so
`dropdownItems` stays empty until the Asset API returns friendly
filenames. Meanwhile `modelValue` already holds the saved asset and the
watcher only tracks `modelValue`:
df653d6ce1/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (L215-L226)

This watcher runs before assets load, fails to find a match, clears
`selectedSet`, and the placeholder persists:


df653d6ce1/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (L222-L224)

Once the assets API resolves, `dropdownItems` recomputes but nothing
resyncs because the watcher never sees that change. Desktop doesn’t hit
this because it still reads from `widget.options.values` immediately.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7005-fix-loader-node-widget-value-shows-placeholder-instead-of-filename-on-cloud-2b86d73d36508125a16de5c9e366b014)
by [Unito](https://www.unito.io)
2025-11-29 18:13:32 -07:00
Christian Byrne
f61bfe666e style: move border and shadow style to outer container to fix topbar badges shadow/border (#7044)
## Summary

Moves the shadow and border from workflow tabs to outer container so
that there is no longer an inconsistency with the badges and topbar
bottom border and shadows.

## Changes

| Before | After |
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
| <img width="1522" height="598" alt="Selection_2473"
src="https://github.com/user-attachments/assets/92aa602c-a018-4b3d-8606-9c00552ffc20"
/> | <img width="1522" height="598" alt="Selection_2472"
src="https://github.com/user-attachments/assets/13c75da3-a938-442a-9bf6-9c30d822a610"
/> |
| <img width="1522" height="598" alt="Selection_2474"
src="https://github.com/user-attachments/assets/e84efd2a-6ae1-44a6-9901-0f902e942b64"
/> | <img width="1522" height="598" alt="Selection_2475"
src="https://github.com/user-attachments/assets/66d4852e-eed7-49f9-8f0f-9e8bbcdcb0b8"
/> |

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7044-style-move-border-and-shadow-style-to-outer-container-to-fix-topbar-badges-shadow-border-2ba6d73d365081b991a3f981db87f434)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-29 17:52:03 -07:00
Christian Byrne
d8795ec997 fix: actionbar can get stuck behind floating menu and not be moved with drag handle (#7034)
Fixes issue where actionbar (and therefore "Run" button) can get stuck
behind the floating menus and not be physically moved (especially on
mobile) leading to an inability to press "Run" button.

`z1010` and `z1000` do not seem to be actual classes in our system.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7034-fix-actionbar-can-get-stuck-behind-floating-menu-and-not-be-moved-with-drag-handle-2b96d73d365081fc87d1ec4446a50e43)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-30 00:22:08 +00:00
Alexander Brown
d3aa8dfc88 A11y/style: Make properties panel use theme colors. Not comprehensive. (#7036)
## Summary

Addressing the theme aspect of #6976 

## Changes

- **What**: Not comprehensive, but swaps many of the colors in the
litegraph panel styles to reference theme tokens.

## Screenshots (if applicable)
<img width="730" height="637" alt="image"
src="https://github.com/user-attachments/assets/1f8b0c0f-f957-487e-96b5-324ed2a6a717"
/>
<img width="721" height="719" alt="image"
src="https://github.com/user-attachments/assets/37f098a3-39f6-4cae-8bcb-601121d106f5"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7036-A11y-style-Make-properties-panel-use-theme-colors-Not-comprehensive-2ba6d73d365081038481c66be68a599a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-29 16:06:29 -08:00
Christian Byrne
2cce0fe611 style: make feedback button icon-only after mobile breakpoint (#7043)
## Summary

On mobile breakpoint, change the "Feedback" button to only show an icon,
preserving crucial space on the breadcrumb axis.

## Changes

| Before | After |
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
| <img width="732" height="1458" alt="Selection_2471"
src="https://github.com/user-attachments/assets/8491a755-5817-4021-a2bd-8dff09a8345f"
/> | <img width="732" height="1458" alt="Selection_2470"
src="https://github.com/user-attachments/assets/6229badb-46c4-4617-9984-5636ad4b803c"
/> |

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7043-style-make-feedback-button-icon-only-after-mobile-breakpoint-2ba6d73d365081489d9adc89eb5ec42b)
by [Unito](https://www.unito.io)
2025-11-29 15:23:48 -07:00
Johnpaul Chiwetelu
b9cb335255 [fix] Add fraction digits props to number input widget (#7033)
Add min-fraction-digits and max-fraction-digits props to InputNumber
component to allow typing decimal values. Without these props, users
could not manually type values like "0.8" even though the increment
buttons worked correctly.

This aligns the input-only variant with the slider variant which
already had these props configured.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7016
## Screenshots (if applicable)



https://github.com/user-attachments/assets/fa205023-13c4-45ac-874b-b241808cf56d



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7033-fix-Add-fraction-digits-props-to-number-input-widget-2b96d73d36508124ac65e466802bbb0c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-29 15:10:04 -07:00
Christian Byrne
2f89eb070c when on cloud, link to cloud version of "getting started" page for "docs" button (#7041)
Changes docs button in bottom left help center to open
https://docs.comfy.org/get_started/cloud when on cloud (as opposed to
https://docs.comfy.org/).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7041-when-on-cloud-link-to-cloud-version-of-getting-started-page-for-docs-button-2ba6d73d365081b79bcaf91cfeb3dffd)
by [Unito](https://www.unito.io)
2025-11-29 13:11:53 -07:00
Benjamin Lu
d94e0720f3 Handle HTML fallbacks for node help and skip blueprint docs (#7021)
explicitly prevents subgraphs from making an api call since they don't
have docs, this was previously reliant on a non-ok resolution

also doesn't try returning anything that has contenttype of text/html to
prevent the markdown renderer from crashing

## Summary
- short-circuit blueprint/subgraph nodes in help: skip doc fetch and
return the node description, avoiding SPA fallback responses
- guard node help fetch against HTML/SPA fallbacks using content-type
checks; treat them as missing and trigger the existing description
fallback
- keep base URL logic unchanged for non-blueprint nodes

## Testing
- pnpm typecheck
- pnpm lint:fix
- pnpm test:unit
2025-11-28 10:09:09 -08:00
Alexander Piskun
fe47a487bb feat(api-nodes-pricing): add prices for ByteDance seedance-1-0-pro-fast model (#7026)
## Summary

Price badges for this PR:
https://github.com/comfyanonymous/ComfyUI/pull/10947

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7026-feat-api-nodes-pricing-add-prices-for-ByteDance-seedance-1-0-pro-fast-model-2b96d73d365081eeb7f7de1dc892389b)
by [Unito](https://www.unito.io)
2025-11-28 10:38:58 -07:00
Benjamin Lu
63f68543e4 [feat] Show "Finished in" duration for completed jobs in cloud (#6895)
## Summary
In cloud distribution, completed jobs now show "Finished in Xh Ym Zs" as
the primary text instead of the filename.

- Uses `formatDuration` to display time as `1h 30m 45s`, `30m 45s`, or
`45s`
- Gated with `isCloud` - non-cloud continues to show filename
- Added i18n key `queue.completedIn` for localization

Filename is not fetchable right now in cloud. This is what design wanted
as the alternative.

<img width="679" height="1097" alt="image"
src="https://github.com/user-attachments/assets/291deb42-77d8-4de9-b4f8-ee65f3c25011"
/>

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-27 16:41:45 -07:00
Johnpaul Chiwetelu
9806ba807a [fix] Re-encode cloud-subscription video to VP9 for Safari compatibility (#7006)
The original video was encoded with AV1 codec which Safari does not
support, causing "Plug-in handled load" errors. Re-encoded to VP9
which is supported across all major browsers including Safari 14.1+.

 Summary

- Re-encode cloud-subscription.webm from AV1 to VP9 codec for Safari
browser compatibility

  Problem

Safari does not support AV1 codec in webm containers, causing the cloud
subscription video to
  fail with "Plug-in handled load" error.

  Solution

Re-encoded the video using VP9 codec (libvpx-vp9) which is supported by
all major browsers
  including Safari 14.1+.

  Test plan

  - Verify video plays correctly in Safari
  - Verify video plays correctly in Chrome/Firefox/Edge


[cloud-subscription.webm](https://github.com/user-attachments/assets/ab2dcaa1-f3b3-47c9-84d0-9fc8ea8c6f17)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7006-fix-Re-encode-cloud-subscription-video-to-VP9-for-Safari-compatibility-2b86d73d365081ad9262f97fc1ebb5ee)
by [Unito](https://www.unito.io)
2025-11-27 16:41:00 -07:00
Simula_r
7433f470fc Fix/vue nodes banner text (#7007)
## Summary

ensure banner text is white

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7007-Fix-vue-nodes-banner-text-2b86d73d365081e1ab60c7013cc9d37c)
by [Unito](https://www.unito.io)
2025-11-27 16:40:27 -07:00
AustinMroz
490bb22bd3 Remove app.graph usage from widgetInput code (#7008)
Resolves #7000

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7008-Remove-app-graph-usage-from-widgetInput-code-2b86d73d3650819eb1bcc98130f18cbe)
by [Unito](https://www.unito.io)
2025-11-27 16:39:53 -07:00
Comfy Org PR Bot
895775c319 1.34.2 (#7009)
Patch version increment to 1.34.2

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7009-1-34-2-2b86d73d36508119ba75d4b9834d8786)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-27 16:38:31 -07:00
JonatanAtila
e248ecfa4e feat(i18n): add Portuguese (Brazil) locale (pt-BR) (#6943)
Finalmente o idioma em Portugues do Brasil verá a luz do dia (se tudo
correr bem).

What has been done:

- Added pt-BR to .i18nrc.cjs and settings
- Included loaders in src/i18n.ts and apps/desktop-ui/src/i18n.ts
- Now Portuguese (BR) is displayed in the language selector
- Created empty main.json, commands.json, settings.json and
nodeDefs.json files to be populated by CI

- Checklist: the language appears in the dropdown list, selection occurs
without errors, the fallback to English, in case technical terms have no
translation, is working correctly.

- I will maintain the pt-br translation and review for as long as
necessary.

---------

Co-authored-by: Comfy Contributor <dev@example.com>
2025-11-27 15:46:27 -07:00
Kelly Yang
923695ffde fix(ui): update button style for mask editor (#6991)
## Summary

Updated the styling of the `resetToDefault` button to match the visual
design and UX behavior (hover states, dimensions) of other buttons in
the `TopBarHeader`

## Changes

Applied the standard top bar button CSS classes to the resetToDefault
button to ensure visual consistency. @jtydhr88


## Review Focus

Please verify that the button alignment and hover effects now match the
adjacent UI elements.

## Screenshots
<img width="2746" height="2300" alt="ScreenShot_2025-11-27_010713_634"
src="https://github.com/user-attachments/assets/758e108f-7c38-4b51-a236-fee2e049186e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6991-fix-ui-update-button-style-for-mask-editor-2b86d73d3650813ea3cbd28203239cd8)
by [Unito](https://www.unito.io)
2025-11-27 15:43:55 -07:00
Comfy Org PR Bot
df653d6ce1 1.34.1 (#6986)
Patch version increment to 1.34.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6986-1-34-1-2b86d73d365081f0b515e6392ac90be8)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-27 13:07:03 -07:00
Alexander Piskun
28dc9314d2 feat(api-nodes-pricing): add prices for Kling-v2-5-turbo for node KlingStartEndFrame (#6996)
## Summary

Pricing for new model for this node

## Screenshots (if applicable)

<img width="1174" height="422" alt="Screenshot From 2025-11-27 14-44-38"
src="https://github.com/user-attachments/assets/78edb4d2-3aef-427c-98d5-ea9b6b18b203"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6996-feat-api-nodes-pricing-add-prices-for-Kling-v2-5-turbo-for-node-KlingStartEndFrame-2b86d73d36508171ba2cd186248228b7)
by [Unito](https://www.unito.io)
2025-11-27 13:06:44 -07:00
Johnpaul Chiwetelu
3acf9aae0a fix: add filter for combo widgets (#6999)
This pull request introduces a minor enhancement to the
`WidgetSelectDefault.vue` component. The change conditionally enables
the filtering feature in the select dropdown when there are more than
four options, improving usability for larger lists.


<img width="932" height="622" alt="Screenshot 2025-11-27 160239"
src="https://github.com/user-attachments/assets/76f6080f-13c7-4fa9-a813-2799af15363e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6999-fix-add-filter-for-combo-widgets-2b86d73d365081f994e5caf78e64a196)
by [Unito](https://www.unito.io)
2025-11-27 13:06:22 -07:00
Alexander Brown
a055241e2e Change node-component-header-surface color variable (#6990)
## Summary

Light mode node header color update.
2025-11-27 16:15:59 +01:00
Jin Yi
9d131a4267 [feat] Add right-click context menu to MediaAssetCard (#6844)
## Summary
- Add right-click context menu functionality to MediaAssetCard
- Separate context menu into its own component
(MediaAssetContextMenu.vue)
- Ensure only one context menu is visible at a time within the same tab

## Changes
- Add `MediaAssetContextMenu.vue` - new component for context menu
- Update `MediaAssetCard.vue` - show context menu on right-click and
more button click
- Delete `MediaAssetMoreMenu.vue` - consolidated into context menu
- Delete `MediaAssetButtonDivider.vue` - unused
- Update `AssetsSidebarTab.vue` - add context menu state management
- Refactor `useMediaAssetActions.ts`

## Screenshot

[screen-capture.webm](https://github.com/user-attachments/assets/6fe414ef-b134-4fbe-98aa-6437bb354b41)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-26 23:02:32 -07:00
Christian Byrne
c57ceaf826 fix: add missing translations (#6970)
## Summary

Adds translations for things identified as "always English" during QA
testing.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6970-fix-add-missing-translations-2b86d73d365081ffa58ccef156d28028)
by [Unito](https://www.unito.io)
2025-11-26 18:25:49 -07:00
Christian Byrne
29dbfa3f60 fix: Vue Node <-> Litegraph node height offset normalization (#6966)
## Summary

Changes the layout store to treat node sizes as body-only measurements
while LiteGraph continues to reason about full heights. DOM-driven
updates are tagged with `LayoutSource.DOM`, which lets the store strip
the title height exactly once before persisting. That classification (a
new mutation source - `LayoutSource.DOM`) is accurate because those
mutations are triggered by the browser’s layout engine via
ResizeObserver, rather than by direct calls into the layout APIs (e.g.,
`moveNodeTo`, `useNodeDrag`). So all sources are:

- `LayoutSource.DOM`: browser layout/ResizeObserver measurements that
include the title bar
- `LayoutSource.Vue`: direct Vue-driven mutations routed through the
layout store
- `LayoutSource.Canvas`: legacy LiteGraph/canvas updates that will be
phased out over time
- `LayoutSource.External`: for multiplayer or syncing with a when going
online after making changes offline (in teams/workspace)

When layout state flows back into LiteGraph we add the title height just
in time for `liteNode.setSize`, so LiteGraph’s rendering stays
unchanged. This makes Vue node resizing and workflow persistence
deterministic - multiline widgets hold their dimensions across reloads
because every path that crosses the layout/LiteGraph boundary performs
the same normalization.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6966-normalize-height-at-sites-2b76d73d365081b6bcb4f4ce6a27663a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-26 17:37:24 -07:00
Christian Byrne
83f04490ba fix: don't use registry when only checking for presence of missing nodes (#6965)
Changes the client-side validation of whether a graph has missing nodes
to use a simpler check (is the node name in the node defs from
`/object_info`), rather than using the Comfy Node Registry API
(api.comfy.org). The Registry API is still needed to get full
metadata/info about missing nodes, but that can be deferred until the
user actually needs that info.

This also fixes an issue where a node you coded yourself locally which
is neither a core node nor a node in the registry would be considered
"missing."

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6965-fix-don-t-use-registry-when-only-checking-for-presence-of-missing-nodes-2b76d73d365081e7b8fbcb3f2a1c4502)
by [Unito](https://www.unito.io)
2025-11-26 17:21:16 -07:00
Christian Byrne
c5fe617347 feat: open template via URL in linear mode (#6945)
## Summary

Adds `mode` as a valid url query param when opening template from URL.



https://github.com/user-attachments/assets/8e7efb1c-d842-4953-822a-f3cea7ddecb6

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6945-feat-open-template-via-URL-in-linear-mode-2b76d73d365081d8ad4af3d49a68a4ff)
by [Unito](https://www.unito.io)
2025-11-26 16:44:28 -07:00
Christian Byrne
8b2c1fc45d allow telemetry events to be disabled by name in feature flags (#6946)
## Summary

Adds ability to toggle events on/off by name from dynamic feature flags.
Also makes some events disabled by default (not being used for any
analysis and taking too much of event quota currently).

Note: telemetry is only enabled on cloud - this doesn't affect local
users in any way.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6946-allow-telemetry-events-to-be-disabled-by-name-in-feature-flags-2b76d73d365081ea8676cfbb8217e640)
by [Unito](https://www.unito.io)
2025-11-26 14:20:21 -07:00
Alexander Brown
df66a96976 Feat: Double Click on Image Assets to open the lightbox (#6955)
## Summary

Make it easier to open the lightbox preview.

## Review Focus

The first click still selects the image. I liked the suggestion to
require shift or ctrl to initiate selection and have click go back to
the previous behavior.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6955-Feat-Double-Click-on-Image-Assets-to-open-the-lightbox-2b76d73d365081f0bf0de847add5c820)
by [Unito](https://www.unito.io)
2025-11-26 11:52:54 -08:00
Alexander Brown
96d12330bb Fix: Toolbox position desync (#6962)
## Summary

Toggle the toolbox position check based on visibility, not just when
selection changes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6962-Fix-Toolbox-position-desync-2b76d73d3650818da327c7adee6c1098)
by [Unito](https://www.unito.io)
2025-11-26 19:43:58 +00:00
Simula_r
4b87b1fdc5 fix: remove LOD from vue nodes (#6950)
## Summary

Refactor to remove LOD from vue nodes. Also, hide Litegraph LOD settings
in vue nodes to prevent confusion / stale setting. Keep litegraph LOD
the exact same.

## Changes

- **What**: settings, all LOD related code in vue nodes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6950-fix-remove-LOD-from-vue-nodes-2b76d73d365081568723f8cbc7be7e17)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-26 12:26:05 -07:00
Comfy Org PR Bot
08b256c29d 1.34.0 (#6961)
Minor version increment to 1.34.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6961-1-34-0-2b76d73d3650810785edf2e5a05d40bb)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-26 12:24:25 -07:00
Alexander Brown
e6332046b0 BYOM: Model Import Wizard (#6949)
## Summary

Design alignment for the model import wizard.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6949-BYOM-Model-Import-Wizard-2b76d73d365081a48632c40430e05c93)
by [Unito](https://www.unito.io)
2025-11-25 19:19:16 -08:00
AustinMroz
5fa76e23d9 Reduce width of run button dock zone (#6925)
Tiny PR to make the docking area line up with the new size of the
actionbar after #6723
<img width="550" height="106" alt="image"
src="https://github.com/user-attachments/assets/911d510e-351f-484f-807a-17f5428dea79"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6925-Reduce-width-of-run-button-dock-zone-2b66d73d36508137aa0cc18178172264)
by [Unito](https://www.unito.io)
2025-11-25 18:19:33 -07:00
Simula_r
fcfb5437a9 feat: mobile breakpoint for vue nodes banner (#6942)
## Summary

Add mobile breakpoint to vue nodes banner

## Changes

- **What**: main.json and vue nodes banner

## Screenshots (if applicable)

<img width="500" height="615" alt="image"
src="https://github.com/user-attachments/assets/fd8cc621-c335-41c9-bbee-4ec0ae04b226"
/>
<img width="980" height="615" alt="image"
src="https://github.com/user-attachments/assets/30e17fc2-fc91-44a3-b9f0-85d5146e861b"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6942-feat-mobile-breakpoint-for-vue-nodes-banner-2b66d73d36508139b69afd7e7134b72e)
by [Unito](https://www.unito.io)
2025-11-25 18:18:01 -07:00
Comfy Org PR Bot
5ff3a0ed52 1.33.9 (#6941)
Patch version increment to 1.33.9

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6941-1-33-9-2b66d73d365081cc9b0de993e427287d)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-25 16:59:12 -07:00
Christian Byrne
5fa0295ff5 feat: update About panel (system stats and version info) to work on cloud (#6940)
## Summary

Updates the About Panel in the settings to work with the cloud-specific
`GET /system_stats` response schema.

| Before | After |
| ------ | ----- |
| <img width="1922" height="1436" alt="Selection_2392"
src="https://github.com/user-attachments/assets/3b97bf38-7eeb-4f46-9c59-eb681f5d7401"
/> | <img width="1922" height="1436" alt="Selection_2391"
src="https://github.com/user-attachments/assets/1d30e604-654a-4d48-ba05-4cac3b54c2ba"
/> |

## Screenshots (if applicable)

OSS version stays the same:

<img width="1922" height="1436" alt="Selection_2393"
src="https://github.com/user-attachments/assets/40e1eeeb-fc5a-4ad0-b37f-dc5d0374901e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6940-feat-update-About-panel-system-stats-and-version-info-to-work-on-cloud-2b66d73d365081f69b6fedfe9507ba92)
by [Unito](https://www.unito.io)
2025-11-25 16:34:34 -07:00
Christian Byrne
1348a0934a feat: update node search result rankings with 2025 usage data (#6939)
Updates the usage data for nodes to improve the search result algorithm.
Old data was from 2024. New data is from Nov 2025 and comes from the
search result interactions only (rather than workflow membership). Old
data is normalized against new data's scale and kept in fields where it
wouldn't have been otherwise overwritten.

Here's a helpful visualization of the before and after for the ranking:
https://www.diffchecker.com/Aa5XTN7F/

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6939-feat-update-node-search-result-rankings-with-2025-usage-data-2b66d73d3650818f9bb5d8765f7f7eaa)
by [Unito](https://www.unito.io)
2025-11-25 16:23:21 -07:00
Christian Byrne
01f8e77251 fix: backport workflow fails with PR title has double quotes (#6934)
Fixes issue when PR title or merge commit has double quotes (e.g.,
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19684310613/job/56385939600).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6934-fix-backport-workflow-fails-with-PR-title-has-double-quotes-2b66d73d3650815992dadb6142676754)
by [Unito](https://www.unito.io)
2025-11-25 15:04:31 -07:00
Benjamin Lu
31c03b669e Bump desktop-ui to 0.0.4 (#6933)
This contains a change to clarify to what is the base path issue after
restrictions were tightened.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6933-Bump-desktop-ui-to-0-0-4-2b66d73d3650816e9f3bed7202455ddb)
by [Unito](https://www.unito.io)
2025-11-25 13:46:21 -08:00
Alexander Piskun
c9da19b5b5 feat(api-nodes-pricing): add prices for Veo3FirstLastFrameNode (#6920)
## Summary

Price badges for this PR:
https://github.com/comfyanonymous/ComfyUI/pull/10878

If we can include this in an upcoming release, that would be absolutely
great.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6920-feat-api-nodes-pricing-add-prices-for-Veo3FirstLastFrameNode-2b66d73d365081bdb49be98d953a7a0b)
by [Unito](https://www.unito.io)
2025-11-25 11:56:39 -08:00
Alexander Piskun
10222860eb feat(api-nodes-pricing): add prices for Flux2ProImageNode (#6921)
## Summary

Price badges for https://github.com/comfyanonymous/ComfyUI/pull/10880

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6921-feat-api-nodes-pricing-add-prices-for-Flux2ProImageNode-2b66d73d365081f2b269c77df7ef93d6)
by [Unito](https://www.unito.io)
2025-11-25 11:56:10 -08:00
Benjamin Lu
4597b7e600 [feat] Improve queue job item UX based on design feedback (#6893)
## Summary
- Running jobs now show cancel button at all times (always visible, not
just on hover)
- Cancel/delete buttons use destructive red styling by default with
hover state
- Changed pending job icon from clock to loader-circle with spin
animation
- Fixed icon buttons to be square (size-6) instead of rectangular
- Added TODO comment for future declarative button config system
- Pending hint ("Job added to queue") now shows only once per entry and
no longer resets when other jobs update
- Spinner animation now applies only to the pending loader icon;
completed/check icons no longer spin
- Queue overlay hover/active state also triggers when hovering the top
menu bar so controls stay visible

## Design Spec

https://www.notion.so/comfy-org/Design-Queue-Dialog-Job-Ordering-and-Cancel-Button-Visibility-2b46d73d365081748a43d5cc9fbe2639

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6893-feat-Improve-queue-job-item-UX-based-on-design-feedback-2b56d73d365081a2bc7ef6f6fea1c739)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-25 11:23:39 -08:00
AustinMroz
6782d04f00 Support display_name on frontend (#6922)
The v3 schema allows defining a `display_name` on inputs, but this was
previously ignored on the frontend. It is now used to designate a
default value for the label of a widget or input.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6922-Support-display_name-on-frontend-2b66d73d365081d992cbea07abc27a0f)
by [Unito](https://www.unito.io)
2025-11-25 10:36:14 -08:00
Benjamin Lu
4bb5c12fac Add cloud backport tagging workflow (#6896)
## Summary
- add workflow to tag merged backport-labeled PRs into cloud/* with
cloud/v<package.json version>
- validate branch/version alignment and skip when tag already exists
- use .nvmrc (Node 24) for setup-node

## Question
- This workflow expects the version bump to already be in the PR when
you merge, as it fails if the tag already exists to keep immutability.
Should we autobump in this case?
2025-11-24 22:56:33 -08:00
Alexander Brown
8b5cfe7e55 Lint: Adding more checks for non internationalized strings (#5625)
## Summary

Catch more user visible (or audible) text that isn't
internationalizable.

## Changes

- **What**: Linter now checks other attributes for raw text.

## Review Focus

What other properties have leaked English text to non-English locales
that aren't in here?
2025-11-24 21:55:47 -08:00
Alexander Brown
135169003f Devex: Remove Importmap plugin (#6899)
## Summary

See [this
page](https://www.notion.so/comfy-org/Remove-importmap-and-replace-with-better-solution-if-it-exists-2ab6d73d3650801d83afe006fa0d9929?source=copy_link).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6899-Devex-Remove-Importmap-plugin-2b66d73d365081b28167c9ae70100092)
by [Unito](https://www.unito.io)
2025-11-24 20:39:46 -08:00
Alexander Brown
d58a464c9c Style: Widget spacing and Markdown padding (#6902)
## Summary

Less padding, looks nice.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6902-Style-Widget-spacing-and-Markdown-padding-2b66d73d365081cca409dbabc7b883bb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 18:31:21 -08:00
Alexander Brown
e54b972550 Chore: Update CODEOWNERS (#6901)
## Summary

Add Global Owners and a team, update/remove references to some cherished
former colleagues.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6901-Chore-Update-CODEOWNERS-2b66d73d365081f8b687ef678567b30a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
2025-11-24 17:34:41 -08:00
Alexander Brown
9bd63dbe6a Cleanup: Consistent use of Nodes 2.0 in user facing strings. (#6898)
https://github.com/Comfy-Org/ComfyUI_frontend/issues/6888

## Summary

Did a quick pass to find user facing strings. Not sure if this gets them
all.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6898-Cleanup-Consistent-use-of-Nodes-2-0-in-user-facing-strings-2b66d73d36508124aee2f5f8373a4b60)
by [Unito](https://www.unito.io)
2025-11-24 17:26:16 -08:00
Johnpaul Chiwetelu
a21c813d11 Style button widgets (#6900)
This pull request introduces improvements to widget customization and UI
consistency in the application. The most notable changes are the
addition of support for icon classes in widget options, updates to
button rendering logic, and enhanced visual consistency for button
components.

Widget customization enhancements:
* Added an optional `iconClass` property to the `IWidgetOptions`
interface in `widgets.ts`, allowing widgets to specify custom icons.

UI and rendering updates:
* Updated `WidgetButton.vue` to render the widget label and, if
provided, an icon using the new `iconClass` option. Also standardized
button styling and label usage.
* Improved button styling in `WidgetRecordAudio.vue` for better visual
consistency with other components.
<img width="662" height="534" alt="Screenshot 2025-11-25 at 01 36 45"
src="https://github.com/user-attachments/assets/43bbe226-07fd-48be-9b98-78b08a726b1b"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6900-Style-button-widgets-2b66d73d3650818ebeadd9315a47ba0f)
by [Unito](https://www.unito.io)
2025-11-25 01:12:30 +00:00
Alexander Brown
df373af987 Feat: Restore settings button to the sidebar. (#6892)
## Summary

Opens settings with a single click!
<img width="247" height="252" alt="image"
src="https://github.com/user-attachments/assets/c571d5e0-4973-4570-a542-fff343364ec0"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6892-Feat-Restore-settings-button-to-the-sidebar-2b56d73d36508102a48aec318d468303)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 12:23:29 -08:00
Alexander Brown
c06a7279e2 Style: Grid for widgets (#6891)
## Summary

Keeps the controls and widgets a consistent width, but lets the size be
more flexible

## Screenshots

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6891-Style-Grid-for-widgets-2b56d73d365081a29c30d337f3be1af6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 11:46:24 -08:00
Benjamin Lu
ffc17c054b Proposal: Run Storybook CI on all pull requests (#6880)
## Summary
Allow the Storybook workflow to run on every PR branch instead of only
`main`.

## Changes
- **What**: remove the `branches: [main]` filter from the Storybook CI
workflow so it triggers for all pull_request events.

## Review Focus
- Confirm broader triggering is desired for all branches while Chromatic
deployment remains restricted to `version-bump-*` or manual runs.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6880-Run-Storybook-CI-on-all-pull-requests-2b56d73d36508169a32afcf50f8e8fd8)
by [Unito](https://www.unito.io)

Co-authored-by: sno <snomiao@gmail.com>
2025-11-23 23:29:47 -08:00
Comfy Org PR Bot
ddb00d02d5 1.33.8 (#6885)
Patch version increment to 1.33.8

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6885-1-33-8-2b56d73d3650815c8660c30e7d625bdb)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-23 22:33:34 -08:00
Benjamin Lu
4815d6b14c Add test id for startup status text (#6882)
## Summary
- add `data-testid="startup-status-text"` to the status text in
StartupDisplay
- keep the status element targetable for Playwright masks

## Why
Desktop’s Playwright tests mask the troubleshooting version line; this
test id is needed there to keep screenshots stable across releases.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6882-Add-test-id-for-startup-status-text-2b56d73d365081b6a2e4ddca9aa985a4)
by [Unito](https://www.unito.io)
2025-11-23 22:54:14 -07:00
Benjamin Lu
a9653ba9c7 Remove unsupported workflow description fields (#6881)
## Summary
Remove top-level `description` keys from workflows because they are not
valid in GitHub Actions workflow syntax (see
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax?utm_source=chatgpt.com).

## Changes
- **What**: delete the unsupported `description:` field from all
workflow YAMLs under `.github/workflows/`.

## Review Focus
- Confirm workflows still show intended names and triggers without the
invalid `description` metadata.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6881-Remove-unsupported-workflow-description-fields-2b56d73d365081ed9f20eb7f57956bc6)
by [Unito](https://www.unito.io)
2025-11-23 22:53:51 -07:00
Christian Byrne
30bafcd019 hide "unload models" and "unload cache" menu entries on cloud (#6879)
Hides these features which the user does not need when on cloud.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6879-hide-unload-models-and-unload-cache-menu-entries-on-cloud-2b46d73d3650816a8e22e913a848e4ac)
by [Unito](https://www.unito.io)
2025-11-23 22:53:18 -07:00
Christian Byrne
b789791fd9 fix: workflow that creates release branch fails (#6878)
Fixes 'create-release-branch' workflow. The script was emitting a
multiline output using `echo "results<<'EOF'" … echo "EOF"`. GitHub
treats the opening delimiter literally (`'EOF'` with quotes). Because
the closing line omits the quotes, the runner never sees a matching
terminator, so it aborts the file-command write and marks the step as
failed even though the preceding git pushes succeeded.

Example of error:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19520246619/job/55881876566

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6878-fix-workflow-that-creates-release-branch-fails-2b46d73d365081549651eacacd5cbfec)
by [Unito](https://www.unito.io)
2025-11-23 22:51:47 -07:00
Christian Byrne
723f53751e fix: duplicate "refresh node definitions" in menu entries (#6876)
The "Refresh node definitions" menu entry was added in the Manager
upstream but while in development was also added in a separate commit,
leading to duplicate menu entries:

<img width="1460" height="324" alt="image"
src="https://github.com/user-attachments/assets/66347cb3-1c52-457e-a4f1-8b32b615a1ca"
/>

This PR removes the second one.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6876-fix-duplicate-refresh-node-definitions-in-menu-entries-2b46d73d365081b98bdfcc60dc9bad36)
by [Unito](https://www.unito.io)
2025-11-23 22:51:36 -07:00
Christian Byrne
2539a7d2ce fix: tabindex prop should be number type in MultiSelect component (#6875)
Change to use number prop to fix warnings:

```
WorkflowTemplateSelectorDialog.vue:7 [Vue warn]: Invalid prop: type check failed for prop "tabindex". Expected Number with value 0, got String with value "0".
  at <MultiSelect modelValue= [] onUpdate:modelValue=fn class="w-[250px]"  ... >
  at <MultiSelect modelValue= [] onUpdate:modelValue=fn search-query=""  ... >
  at <BaseModalLayout content-title="Get Started with a Template" class="workflow-template-selector-dialog" maximized=false >
  at <WorkflowTemplateSelectorDialog ref_for=true onClose=fn<hide> maximized=false >
  at <BaseTransition onEnter=fn onAfterEnter=fn<bound onAfterEnter> onBeforeLeave=fn<bound onBeforeLeave>  ... > 
  at <Transition name="p-dialog" onEnter=fn<bound onEnter> onAfterEnter=fn<bound onAfterEnter>  ... > 
  at <Portal appendTo="body" > 
  at <Dialog key="global-workflow-template-selector" visible=true onUpdate:visible=fn<onUpdate:visible>  ... >
  at <GlobalDialog > 
  at <App>
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6875-fix-tabindex-prop-should-be-number-type-in-MultiSelect-component-2b46d73d3650816d8288fec4cc0f7e7f)
by [Unito](https://www.unito.io)
2025-11-23 22:51:26 -07:00
Christian Byrne
c9556d7aff fix: ordering of Vue mode LOD setting initialization in browser tests (#6884)
Fixes test "should toggle LOD based on zoom threshold" failing (example:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19618176782/job/56174006314).

Test execution order:

1. Sets MinFontSizeForLOD = 8
2. Calls setup()
3. App initializes → useVueNodeLifecycle runs → useRenderModeSetting
executes with immediate: true
4. Overrides setting back to 0 (Vue mode value)
5. LOD never activates (threshold = 0)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6884-fix-ordering-of-Vue-mode-LOD-setting-initialization-in-browser-tests-2b56d73d365081209676fb14d2c04d28)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 22:51:07 -07:00
AustinMroz
58b051a473 Share button and Assets Panel in Linear Mode (#6794)
- Re-enables the share button in Linear Mode and have it export the
current workflow
- Not as nice as having it copy an actual URL, but good enough for the
interim and it help with dead space
- Display the Media Assets Panel on the left hand side to replace the
removed Queue Panel

<img width="806" alt="image"
src="https://github.com/user-attachments/assets/93786dfa-8fbb-4368-8594-b9c98bbeb79e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6794-Share-button-and-Assets-Panel-in-Linear-Mode-2b26d73d36508178aef9ededa38d47f1)
by [Unito](https://www.unito.io)
2025-11-23 12:25:30 -07:00
Alexander Brown
0b33470744 Minor: transformState and setting error cleanup (#6841)
## Summary

Fixes the routing of TransformState through the node and the console
error from a setting that ends up being undefined via
useRenderModeSetting.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6841-Minor-transformState-and-setting-error-cleanup-2b46d73d3650817a8da7fca5bc56ea9a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 12:24:37 -07:00
Comfy Org PR Bot
fb3ce74d2f 1.33.7 (#6856)
Patch version increment to 1.33.7

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6856-1-33-7-2b46d73d365081c4b709d125df63c98d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 11:47:49 -07:00
Comfy Org PR Bot
86d3f0ebd5 1.33.6 (#6837)
Patch version increment to 1.33.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6837-1-33-6-2b46d73d3650815194b4fd885b13b574)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 11:31:54 -07:00
Jin Yi
09c888e338 Fix: Selected assets count not updating in Imported tab (#6842)
## Summary
- Fix bug where the "Selected assets count" displayed as 0 in the
Imported tab when selecting assets

## Root Cause
The `getOutputCount` function was returning `0` when
`user_metadata.outputCount` was not present.

- **Generated tab**: Works correctly because `outputCount` metadata is
set during generation
- **Imported tab**: `outputCount` metadata is never set, so it always
returns `0` → selected count shows as 0

## Solution
Changed the default return value from `0` to `1` when `outputCount`
metadata is not present, ensuring every asset counts as at least 1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2025-11-23 00:57:36 -08:00
Alexander Brown
6d41e8b6e4 Feat: Load Image (from Outputs) support in Vue Nodes (#6836)
## Summary

Expose the Auto-refresh and manual refresh controls.
Fixes an issue with the Dropdown where it was index dependent so wasn't
updating correctly as new items came in.

## Screenshots

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6836-Feat-Load-Image-from-Outputs-support-in-Vue-Nodes-2b46d73d365081f1b44fcf2054d653da)
by [Unito](https://www.unito.io)
2025-11-22 23:59:00 -08:00
Terry Jia
274f77869b Fix: Opening mask editor on context menu (#6825)
## Summary

Fix issue of opening mask editor on context menu, reported in
https://github.com/Comfy-Org/ComfyUI_frontend/issues/6824

## Screenshots (if applicable)


https://github.com/user-attachments/assets/666d2769-d848-4b08-b54b-0cf5ed799b35

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6825-Fix-Opening-mask-editor-on-context-menu-2b36d73d3650810781a3c25a23ba488a)
by [Unito](https://www.unito.io)
2025-11-22 20:46:29 -05:00
Alexander Brown
f5c9f69678 Style: Fix the filter/search/sort controls on the Template Select Modal (#6835)
## Summary

Background and text colors.

## Screenshot
<img width="1186" height="148" alt="image"
src="https://github.com/user-attachments/assets/0ff3b0d5-6aae-45c5-9ebf-060a9973489c"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6835-Style-Fix-the-filter-search-sort-controls-on-the-Template-Select-Modal-2b36d73d3650816b9850e1b9f7feb25e)
by [Unito](https://www.unito.io)
2025-11-22 15:30:14 -08:00
Alexander Brown
c1e237255a Fix: TextArea context menu (#6834)
## Summary

Allow the default browser context menu within textareas on Vue Nodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6834-Fix-TextArea-context-menu-2b36d73d3650814e9706e76163dda59a)
by [Unito](https://www.unito.io)
2025-11-22 15:18:04 -08:00
AustinMroz
a91b9f288f When filtering by IO type, put wildcards last (#6829)
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/628057b2-4844-490d-9899-fce82d8e2d58"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/2825f15f-c084-4e3d-8b22-cc0aa3febdce"
/> |

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6829-When-filtering-by-IO-type-put-wildcards-last-2b36d73d36508196a41fed40b62f5b84)
by [Unito](https://www.unito.io)
2025-11-22 13:43:05 -07:00
Tristan Sommer
4adcf09cca GPU accelerated maskeditor rendering (#6767)
## GPU accelerated brush engine for the mask editor

- Full GPU acceleration using TypeGPU and type-safe shaders
- Catmull-Rom Spline Smoothing
- arc-length equidistant resampling
- much improved performance, even for huge images
- photoshop like opacity clamping for brush strokes
- much improved soft brushes
- fallback to CPU fully implemented, much improved CPU rendering
features as well

### Tested Browsers
- Chrome (fully supported)
- Safari 26 (fully supported, prev versions CPU fallback)
- Firefox (CPU fallback, flags needed for full support)



https://github.com/user-attachments/assets/b7b5cb8a-2290-4a95-ae7d-180e11fccdb0



https://github.com/user-attachments/assets/4297aaa5-f249-499a-9b74-869677f1c73b



https://github.com/user-attachments/assets/602b4783-3e2b-489e-bcb9-70534bcaac5e

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6767-GPU-accelerated-maskeditor-rendering-2b16d73d3650818cb294e1fca03f6169)
by [Unito](https://www.unito.io)
2025-11-22 09:07:16 -05:00
Christian Byrne
1dbb3fc1b9 make vue node settings appear higher in the settings dialog (#6820)
makes setting groups/categories be sorted by highest internal setting
field `sortOrder` and adds high `sortOrder` values to the Vue Nodes
(Nodes 2.0) settings.


<img width="2282" height="1872" alt="Selection_2371"
src="https://github.com/user-attachments/assets/71e7e76b-4637-42b5-9f0c-2617622cda23"
/>
2025-11-21 20:32:18 -08:00
Alexander Brown
d6c5b33fce Fix: Vue <--> Litegraph scaling logic. (#6745)
Consistent names and order of operations. I think this fixes the
resizing too.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6745-refactor-Reorganizing-scaling-logic-2b06d73d365081eebc8ff93c87fa69fb)
by [Unito](https://www.unito.io)
2025-11-22 01:52:20 +00:00
Alexander Brown
f5608435b4 Fix: Clear apiKey on failed auth (#6816)
## Summary

Handles the case where an API key is structurally valid but not in our
DB.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6816-Fix-Clear-apiKey-on-failed-auth-2b26d73d3650817ab34edfa380795178)
by [Unito](https://www.unito.io)
2025-11-21 17:26:39 -07:00
Alexander Brown
9da82f47ef Feat: Alt+Drag to clone - Vue Nodes (#6789)
## Summary

Replicate the alt+drag to clone behavior present in litegraph.

## Changes

- **What**: Simplify the interaction/drag handling, now with less state!
- **What**: Alt+Click+Drag a node to clone it

## Screenshots (if applicable)



https://github.com/user-attachments/assets/469e33c2-de0c-4e64-a344-1e9d9339d528



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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6789-WIP-Alt-Drag-to-clone-Vue-Nodes-2b16d73d36508102a871ffe97ed2831f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-21 14:16:03 -08:00
Alexander Brown
a8d6f7baff Feat: Show Progress Text on Vue Nodes, Markdown for Preview as Text (#6805)
## Summary

Maps the progressText / text preview to a readonly Markdown widget.

## Review Focus

Anything else to tweak about this while I'm in here?

## Screenshots
<img width="1107" height="593" alt="image"
src="https://github.com/user-attachments/assets/a445c07a-2fcb-480c-976e-bda6ff343f14"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6805-Feat-Show-Progress-Text-on-Vue-Nodes-2b26d73d3650814d93c4f2fbf0e00095)
by [Unito](https://www.unito.io)
2025-11-21 13:54:53 -08:00
Christian Byrne
a7daa5071c fix: backport workflow fails when label description has single quote (#6814)
Fixes issue with backporting workflow when the target PR has a label
whose description has quotes
([example](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19580741998/job/56077744515))

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6814-fix-backport-workflow-fails-when-label-description-has-single-quote-2b26d73d36508143bbefe6c4314fd370)
by [Unito](https://www.unito.io)
2025-11-21 14:23:36 -07:00
Jin Yi
639975b804 fix: Prevent multiple asset card popovers from opening simultaneously (#6808)
## Summary

Ensures only one MediaAssetCard popover is open at a time.

## Changes

- Added `hide()` method exposure in MoreButton component
- Implemented parent-level state management for tracking open popover
- Added VueUse `whenever` to auto-close other popovers when one opens

## Test Plan

- Open multiple asset cards' more menus
- Verify only one popover remains open at a time

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6808-fix-Prevent-multiple-asset-card-popovers-from-opening-simultaneously-2b26d73d365081e1b3f0d97b869cedc5)
by [Unito](https://www.unito.io)
2025-11-21 14:01:19 -07:00
Luke Mino-Altherr
ff0d385db8 [bugfix] Fix double-click required after pasting URL in upload model dialog (#6801)
## Summary
Fixed an issue where users had to click twice to continue after pasting
a URL in the upload model dialog - once to blur the input, then again to
click the button.

## Changes
- **What**: Replaced `UrlInput` with plain `InputText` in
`UploadModelUrlInput` to emit value immediately on input instead of only
on blur
- **Cleanup**: Moved URL cleaning/normalization to the `fetchMetadata`
handler, removed unused `disableValidation` prop from `UrlInput`
component

## Review Focus
- URL normalization logic in `useUploadModelWizard.ts`

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6801-bugfix-Fix-double-click-required-after-pasting-URL-in-upload-model-dialog-2b26d73d3650811881aed0cc064efcc7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 11:05:36 -08:00
Luke Mino-Altherr
6e2e591937 refactor: change model button terminology from Upload to Import (#6800)
## Summary
Changes user-facing text from "Upload" to "Import" for the model import
feature, as "Import" better describes importing models from external
sources like Civitai.

## Changes
- Updated button icon from `upload` to `package-plus`
- Changed all user-facing text in English locale from "Upload" to
"Import"

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6800-refactor-change-model-button-terminology-from-Upload-to-Import-2b26d73d365081059ce6e4a548f943ce)
by [Unito](https://www.unito.io)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 21:47:02 -08:00
Comfy Org PR Bot
27fcc4554f 1.33.5 (#6798)
Patch version increment to 1.33.5

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6798-1-33-5-2b26d73d3650814fb062c8e2c1602ac6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-20 21:06:15 -07:00
Alexander Brown
e563c1be75 hotfix: Stop clicks on the textarea from propagating to the node itself (#6788)
## Summary

Selecting text shouldn't drag the node.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6788-hotfix-Stop-clicks-on-the-textarea-from-propagating-to-the-node-itself-2b16d73d3650819c8d0dc427d5758580)
by [Unito](https://www.unito.io)
2025-11-20 21:05:24 -07:00
Jin Yi
5a297e520c fix: disabling queue button (#6797)
## Summary
- Removed the `disabled` prop binding from the queue button that was
incorrectly disabling it when there were missing nodes

## Changes
- Removed `:disabled="hasMissingNodes"` from ComfyQueueButton.vue:13

## Test plan
- [x] Verify queue button is no longer incorrectly disabled when there
are missing nodes
- [x] Verify queue functionality works as expected

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6797-fix-disabling-queue-button-2b26d73d3650810783cedd44fce757be)
by [Unito](https://www.unito.io)
2025-11-20 20:52:30 -07:00
Benjamin Lu
85bec5ee47 Use shared button components in queue overlay (#6793)
## Summary
- replace raw button elements in queue progress overlay UI with shared
IconButton/TextButton/IconTextButton components
- remove forced justify-start from IconTextButton base and add explicit
alignment where needed
- keep queue overlay actions consistent with button styling patterns

## Testing
- pnpm typecheck
- pnpm lint:fix

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6793-Use-shared-button-components-in-queue-overlay-2b26d73d3650814d9ebfebba74226036)
by [Unito](https://www.unito.io)
2025-11-20 20:18:33 -07:00
AustinMroz
bdf6d4dea2 Allow updating position and mode on missing nodes (#6792)
When a node is missing, attempts to serialize it will return the "last
known good" serialization to ensure that the node will still be
functional in the future (the node pack is installed/comfyui is
updated). However, this means even small and safe changes (like moving
the node out of the way or bypassing it so the workflow can be run) will
be discarded on reload.

This is resolved by including the updated position and mode when
returning early.

| Before | After |
| ------ | ----- |
| <img width="360" height="360" alt="before"
src="https://github.com/user-attachments/assets/8452682c-9531-4153-a258-158c634df3e8"
/> | <img width="360" height="360" alt="after"
src="https://github.com/user-attachments/assets/8825ce5e-c4a6-4f4a-be20-97e4aca69964"
/> |

Thanks to @Kosinkadink for bringing this up

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6792-Allow-updating-position-and-mode-on-missing-nodes-2b26d73d365081ed8c22fafe5348c49f)
by [Unito](https://www.unito.io)
2025-11-20 17:07:15 -08:00
Comfy Org PR Bot
b8a796212c 1.33.4 (#6791)
Patch version increment to 1.33.4

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6791-1-33-4-2b16d73d365081f4b675e2d44f4935ca)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-20 17:19:34 -07:00
AustinMroz
bc553f12be Add support for dynamic widgets (#6661)
Adds support for "dynamic combo" widgets where selecting a value on a
combo widget can cause other widgets or inputs to be created.


![dynamic-widgets_00001](https://github.com/user-attachments/assets/c797d008-f335-4d4e-9b2e-6fe4a7187ba7)

Includes a fairly large refactoring in litegraphService to remove
`#private` methods and cleanup some duplication in constructors for
subgraphNodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6661-Add-support-for-dynamic-widgets-2a96d73d3650817aa570c7babbaca2f3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-20 16:53:59 -07:00
Jin Yi
6bb35d46c1 fix: Conditionally hide bottom border in missing nodes modal on non-cloud environments (#6779)
## Summary
- Conditionally hide the bottom border of the missing nodes modal
content when not running in cloud environment
- The footer is not visible in non-cloud environments, so the bottom
border was appearing disconnected

## Changes
- Added conditional `border-b-1` class based on `isCloud` flag in
`MissingNodesContent.vue`
- Keeps top border visible in all environments
- Bottom border only shows in cloud environment where footer is present

## Test plan
- [ ] Open missing nodes dialog in cloud environment - bottom border
should be visible
- [ ] Open missing nodes dialog in non-cloud environment - bottom border
should be hidden
- [ ] Verify top border remains visible in both environments

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6779-fix-Conditionally-hide-bottom-border-in-missing-nodes-modal-on-non-cloud-environments-2b16d73d365081cea1c8c98b11878045)
by [Unito](https://www.unito.io)
2025-11-20 16:52:08 -07:00
Alexander Piskun
68c38f0098 feat(api-nodes-pricing): add Nano-Banana-2 prices (#6781)
## Summary

Change pricing display for Nano Banana 1, and added pricing for Nano
Banana 2.

## Screenshots (if applicable)

<img width="2101" height="963" alt="image"
src="https://github.com/user-attachments/assets/78c922c6-f6d8-47c3-afeb-adf28deb5542"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6781-feat-api-nodes-pricing-add-Nano-Banana-2-prices-2b16d73d3650810a8e8dde8f01ba9f02)
by [Unito](https://www.unito.io)
2025-11-20 09:20:05 -08:00
Comfy Org PR Bot
236247f05f 1.33.3 (#6778)
Patch version increment to 1.33.3

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6778-1-33-3-2b16d73d365081308daaf0a8553c0588)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-20 00:56:00 -08:00
AustinMroz
87d6d18c57 Fix linear mode with vue (#6769)
Previously, entering linear mode from vue mode would cause all nodes to
be set to position 0.

This is fixed by ignoring resize updates with a contentRect of all 0s

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6769-Fix-linear-mode-with-vue-2b16d73d36508188964bcfb8b465dcb1)
by [Unito](https://www.unito.io)
2025-11-19 21:39:38 -08:00
Jin Yi
87106ccb95 [bugfix] Fix execute button incorrectly disabled on empty workflows (#6774)
## Summary

Fixes a bug where the queue/execute button was incorrectly disabled with
a warning icon when creating a new empty workflow, due to stale missing
nodes data persisting from a previous workflow.

## Root Cause

When switching from a workflow with missing nodes to an empty workflow,
the `getWorkflowPacks()` function in `useWorkflowPacks.ts` would return
early without clearing the `workflowPacks.value` ref, causing stale
missing node data to persist.

## Changes

- **`useWorkflowPacks.ts`**: Explicitly clear `workflowPacks.value = []`
when switching to empty workflow
- **`useMissingNodes.test.ts`**: Add test case to verify missing nodes
state clears when switching to empty workflow

## Test Plan

- [x] Added unit test covering the empty workflow scenario
- [x] All 20 unit tests pass
- [x] TypeScript type checking passes
- [x] Manual verification: Create workflow with missing nodes → Create
new empty workflow → Button should be enabled

## Before

1. Open workflow with missing nodes → Button disabled  (correct)
2. Create new empty workflow → Button still disabled  (bug)
3. Click valid workflow → Button enabled 

## After

1. Open workflow with missing nodes → Button disabled 
2. Create new empty workflow → Button enabled  (fixed)
3. Click valid workflow → Button enabled 

[screen-capture
(2).webm](https://github.com/user-attachments/assets/833355d6-6b4b-4e77-94b9-d7964454cfce)

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6774-bugfix-Fix-execute-button-incorrectly-disabled-on-empty-workflows-2b16d73d365081e3a050c3f7c0a20cc6)
by [Unito](https://www.unito.io)
2025-11-19 21:22:32 -08:00
AustinMroz
a20fb7d260 Allow unsetting widget labels (#6773)
https://github.com/user-attachments/assets/af344318-dac2-4611-b080-910cdfa1e87d

Quick followup to #6752
- Adds support for placeholder values in dialogService.prompt
- When label is unset, initial prompt is empty
- Display original widget name as placeholder
- When prompt returns an empty string (as opposed to null for a canceled
operation), remove widget label

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6773-Allow-unsetting-widget-labels-2b16d73d365081ae9f5dd085d0081733)
by [Unito](https://www.unito.io)
2025-11-19 21:14:32 -08:00
Christian Byrne
836cd7f9ba fix: node preview background color (#6768)
## Summary

Changes node previews to use background color from color palette instead
of the menu color.

| Before | After |
| ------ | ----- |
| <img width="1700" height="1248" alt="Selection_2345"
src="https://github.com/user-attachments/assets/cc1d0e97-9551-4f88-8e92-4fe6bcdd8c21"
/> | <img width="2144" height="1138" alt="Selection_2347"
src="https://github.com/user-attachments/assets/16e64be3-3623-4900-ad18-c599a1aee59b"
/> |

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/6576

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6768-fix-node-preview-background-color-2b16d73d365081868644e1e6b7c7e3be)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2025-11-19 21:41:25 -07:00
Luke Mino-Altherr
acd855601c [feat] Add Civitai model upload wizard (#6694)
## Summary
Adds a complete model upload workflow that allows users to import models
from Civitai URLs directly into their library.

## Changes
- **Multi-step wizard**: URL input → metadata confirmation → upload
progress
- **Components**: UploadModelDialog, UploadModelUrlInput,
UploadModelConfirmation, UploadModelProgress, UploadModelDialogHeader
- **API integration**: New assetService methods for metadata retrieval
and URL-based uploads
- **Model type management**: useModelTypes composable for fetching and
formatting available model types
- **UX improvements**: Optional validation bypass for UrlInput component
- **Localization**: 26 new i18n strings for the upload workflow

## Review Focus
- Error handling for failed uploads and metadata retrieval
- Model type detection and selection logic
- Dialog state management across multi-step workflow

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6694-feat-Add-Civitai-model-upload-wizard-2ab6d73d36508193b3b1dd67c7cc5a09)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-19 20:37:22 -08:00
Comfy Org PR Bot
423a2e76bc 1.33.2 (#6762)
Patch version increment to 1.33.2

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6762-1-33-2-2b16d73d365081faa7dec4ac8660105a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 20:29:36 -07:00
Benjamin Lu
26578981d4 Remove queue sidebar tab (#6724)
## Summary
- drop the queue sidebar entry, its component, and the supporting
composable so only the overlay-based queue UI remains
- clean up the related tests and keybindings so nothing references the
removed tab
- prune the unused queue task card components to keep the repo tidy
- remove unused queue sidebar translations and command strings across
all locales

## Testing
- pnpm typecheck
- pnpm lint:fix

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6724-Remove-queue-sidebar-tab-2ae6d73d3650811db0d4c5ad4c5ffc8d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-19 19:50:24 -07:00
Simula_r
38fb53dca8 feat: LOD setting for LG and Vue (#6755)
## Summary

Create a composable for useRenderModeSetting that lets you set a setting
for either vue or litegraph and once set remembers each state
respectively.

```
  useRenderModeSetting(
    { setting: 'LiteGraph.Canvas.MinFontSizeForLOD', vue: 0, litegraph: 8 },
    shouldRenderVueNodes
  )
```

## Screenshots (if applicable)

<img width="1611" height="997" alt="image"
src="https://github.com/user-attachments/assets/621930f2-2d21-4c86-a46d-e3e292d4e012"
/>
<img width="1611" height="997" alt="chrome_Gr1V3P6sJi"
src="https://github.com/user-attachments/assets/eb63b747-487f-4f5e-8fcf-f0d2ff97b976"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6755-feat-LOD-setting-for-LG-and-Vue-2b16d73d365081cbbf09c292ee3c0e96)
by [Unito](https://www.unito.io)
2025-11-19 18:49:58 -08:00
AustinMroz
a832141a45 Support renaming widgets (#6752)
![widget-rename_00002](https://github.com/user-attachments/assets/65205d3e-2c03-480d-916e-0dae89ddbdd9)

Widget labels are saved by serializing the value on inputs. This
requires minor changes to ensure widgets inputs are serialized when
required.

Currently only exposed by right clicking on widgets directly. Should
probably be added to the subgraph config panel in the future.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6752-Support-renaming-widgets-2b06d73d36508196bff2e511c6e7b89b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 19:49:03 -07:00
Benjamin Lu
d1f0211b61 Desktop maintenance: unsafe base path warning (#6750)
Surface unsafe base path validation in the desktop maintenance view and
add an installation-fix auto-refresh after successful tasks.

<img width="1080" height="870" alt="image"
src="https://github.com/user-attachments/assets/26fe61be-fed8-47c0-a921-604f0af018f8"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6750-Desktop-maintenance-unsafe-base-path-warning-2b06d73d36508147aeb4d19d02bbf0f0)
by [Unito](https://www.unito.io)
2025-11-19 18:13:32 -08:00
Comfy Org PR Bot
cc42c2967c 1.33.1 (#6756)
Patch version increment to 1.33.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6756-1-33-1-2b16d73d36508138a2daf1cc8ba88736)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 17:52:38 -07:00
AustinMroz
bb51a5aa76 Add linear mode (#6670)
![linear-mode](https://github.com/user-attachments/assets/d1aa078a-00a8-4e71-86d5-ee929b269c90)

See also: #6642

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6670-Add-linear-mode-2aa6d73d365081d08887e4a6db3a8fa0)
by [Unito](https://www.unito.io)
2025-11-19 16:59:43 -07:00
Benjamin Lu
674d884e79 Remove queue cancel controls (#6723)
## Summary
- remove the interrupt/clear controls from the run button cluster
- drop the queue pending count dependencies that only fed those controls

This is at the request of product design, because this functionality is
being relocated to the queue progress overlay in bl-execution

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6723-Remove-queue-cancel-controls-2ae6d73d365081ddaa63ea9e3447ad7f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-19 16:54:27 -07:00
Benjamin Lu
6f89d9a9f8 Add errors for install dir edge cases (#6733)
## Summary
- show explicit validation errors when the install path lives inside the
desktop app bundle or updater cache
- include the new locale strings for these error prompts so the UI
surfaces actionable guidance

## Testing
- pnpm typecheck
- pnpm lint:fix

## Notes
Desktop types still need to be updated to include the new validation
flags; see https://github.com/Comfy-Org/desktop/pull/1400

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6733-Add-errors-for-install-dir-edge-cases-2af6d73d3650811bada6fc7dd72ecf68)
by [Unito](https://www.unito.io)
2025-11-19 15:53:35 -08:00
Comfy Org PR Bot
08b206f191 1.33.0 (#6753)
Minor version increment to 1.33.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6753-1-33-0-2b06d73d365081658da1ff01bf5e8328)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 16:51:48 -07:00
Johnpaul Chiwetelu
14d94da52b Fix Node Event Handlers for Shift Click (#6262)
This pull request refactors the node selection and pointer interaction
logic in the Vue node graph editor to improve multi-selection behavior,
clarify event handling, and enhance test coverage. The main change is to
defer multi-select toggle actions (such as ctrl+click for
selection/deselection) from pointer down to pointer up, preventing
premature selection state changes and making drag interactions more
robust. The drag initiation logic is also refined to only start dragging
after the pointer moves beyond a threshold, and new composable methods
are introduced for granular node selection control.

**Node selection and pointer event handling improvements:**
* Refactored multi-select (ctrl/cmd/shift+click) logic in
`useNodeEventHandlersIndividual`: selection toggling is now deferred to
pointer up, and pointer down only brings the node to front without
changing selection state. The previous `hasMultipleNodesSelected`
function and related logic were removed for clarity.
[[1]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L18-L35)
[[2]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L57-L73)
[[3]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL112-L116)
[[4]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aR127-R143)
* Added new composable methods `deselectNode` and
`toggleNodeSelectionAfterPointerUp` to `useNodeEventHandlersIndividual`
for more granular control over node selection, and exposed them in the
returned API.
[[1]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084R210-R245)
[[2]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L251-R259)

**Pointer interaction and drag behavior changes:**
* Updated `useNodePointerInteractions` to track pointer down/up state
and only start dragging after the pointer moves beyond a pixel
threshold. Multi-select toggling is now handled on pointer up, not
pointer down, and selection state is read from the actual node manager
for accuracy.
[[1]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R6-R10)
[[2]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R33-R34)
[[3]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R44-R53)
[[4]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R76-R110)
[[5]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R122-R123)
[[6]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1L131-R175)

**Test suite enhancements:**
* Improved and expanded tests for pointer interactions and selection
logic, including new cases for ctrl+click selection toggling on pointer
up, drag threshold behavior, and mocking of new composable methods.
[[1]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR9-R11)
[[2]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR35-R56)
[[3]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR100-R102)
[[4]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL144-R181)
[[5]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL155-R196)
[[6]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL196-R247)
[[7]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL276-R336)
[[8]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR348-R423)
* Updated test setup and assertions for node event handlers, ensuring
selection changes are only triggered at the correct event phase and that
drag and multi-select logic is covered.
[[1]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL4-R7)
[[2]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aR92)

These changes make node selection more predictable and user-friendly,
and ensure drag and multi-select actions behave consistently in both the
UI and the test suite.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/6128



https://github.com/user-attachments/assets/582804d0-1d21-4ba0-a161-6582fb379352
2025-11-19 11:32:20 -08:00
Jin Yi
a521066b25 [feat] Add missing nodes warning UI to queue button and breadcrumb (#6674) 2025-11-19 12:23:24 -07:00
Jin Yi
ada0993572 feat: Improve MediaAssetCard design and add responsive sidebar footer (#6749)
## Summary
Implements design feedback for the asset panel, improving visual
hierarchy, contrast, and responsiveness based on design tokens update.

## Changes

### 🎨 Design System Updates (style.css)
- **New tokens for MediaAssetCard states:**
  - `--modal-card-background-hovered`: Hover state background
  - `--modal-card-border-highlighted`: Selected state border color
- **Updated tag contrast:**
  - Light mode: `smoke-200` → `smoke-400`
  - Dark mode: `charcoal-200` → `ash-800`
- **Registered tokens in Tailwind** via `@theme inline` for proper class
generation

### 🖼️ MediaAssetCard Improvements
- **Added tooltips** to all interactive buttons:
  - Zoom button: "Inspect"
  - More button: "More options"
  - Output count button: "See more outputs"
- **Fixed tooltip event conflicts** by wrapping buttons in tooltip divs
- **Updated hover/selected states:**
  - Hover: Uses `--modal-card-background-hovered` for subtle highlight
- Selected: Uses `--modal-card-border-highlighted` for border only (no
background)
- **Updated placeholder background** to use
`--modal-card-placeholder-background`
- **Tag styling:** Changed from `variant="light"` to `variant="gray"`
for better contrast

### 📦 SquareChip Component
- **Added `gray` variant** that uses `--modal-card-tag-background` token
- Maintains consistency with design system tokens

### 📱 AssetsSidebarTab Responsive Footer
- **Responsive button display:**
  - Width > 350px: Shows icon + text buttons
  - Width ≤ 350px: Shows icon-only buttons
- **Text alignment:** Left-aligns selection count text in compact mode
- **Uses `useResizeObserver`** for automatic width detection

### 🌐 Internationalization
- Added new i18n keys for tooltips:
  - `mediaAsset.actions.inspect`
  - `mediaAsset.actions.more`
  - `mediaAsset.actions.seeMoreOutputs`

### 🔧 Minor Fixes
- **Media3DTop:** Improved text size and icon color for better visual
hierarchy

## Visual Changes
- **Increased contrast** for asset card tags (more visible in both
themes)
- **Hover state** now provides clear visual feedback
- **Selected state** uses border highlight instead of background fill
- **Sidebar footer** gracefully adapts to narrow widths

## Related
- Addresses feedback from:
https://www.notion.so/comfy-org/Asset-panel-feedback-2aa6d73d3650800baacaf739a49360b3
- Design token updates by @Alex Tov

## Test Plan
- [ ] Verify asset card hover states in both light and dark themes
- [ ] Verify asset card selected states show highlighted border
- [ ] Test tooltips on all MediaAssetCard buttons
- [ ] Resize sidebar to < 350px and verify footer shows icon-only
buttons
- [ ] Resize sidebar to > 350px and verify footer shows icon + text
buttons
- [ ] Verify tag contrast improvement in both themes
- [ ] Test 3D asset placeholder appearance

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6749-feat-Improve-MediaAssetCard-design-and-add-responsive-sidebar-footer-2b06d73d365081019b90e110df2f1ae8)
by [Unito](https://www.unito.io)
2025-11-19 12:13:03 -07:00
Benjamin Lu
e42715086e Implement workflow progress panel (#6092)
Adds a workflow progress panel component underneath the
`actionbar-container`.

I suggest starting a review at the extraneous changes that were needed.
Including but not limited to:

- `get createTime()` in queueStore
- `promptIdToWorkflowId`, `initializingPromptIds`, and
`nodeProgressStatesByPrompt` in executionStore
- `create_time` handling in v2ToV1Adapter
- `pointer-events-auto` on ComfyActionbar.vue

The rest of the changes should be contained under
`QueueProgressOverlay.vue`, and has less of a blast radius in case
something goes wrong.

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-18 22:43:49 -08:00
Comfy Org PR Bot
92968f3f9b 1.32.6 (#6744)
Patch version increment to 1.32.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6744-1-32-6-2b06d73d365081948f0ff7c6b359bfd8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 22:14:12 -08:00
Alexander Brown
73692464ef Hotfix: Make the actionbar at least higher than the comfyui-body-bottom (#6743)
## Summary

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/6739

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6743-Hotfix-Make-the-actionbar-at-least-higher-than-the-comfyui-body-bottom-2b06d73d365081adb107e6aa2c4ca813)
by [Unito](https://www.unito.io)
2025-11-19 01:59:59 +00:00
Alexander Brown
61b3ca046a Cleanup: Missed the schema and registration for the removed unused widgets (#6742)
## Summary

Just a cleanup following up on
https://github.com/Comfy-Org/ComfyUI_frontend/pull/6741
2025-11-19 01:47:24 +00:00
Alexander Piskun
f8912ebaf4 feat(api-nodes-pricing): add pricing for gemini-3-pro-preview model (#6735)
## Summary

Pricing is a little bit higher then for `2.5` models:

https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models-3

## Screenshots (if applicable)

<img width="1202" height="821" alt="Screenshot From 2025-11-18 19-28-21"
src="https://github.com/user-attachments/assets/c4a279f3-8981-4424-93c5-efa7b68f0578"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6735-feat-api-nodes-pricing-add-pricing-for-gemini-3-pro-preview-model-2af6d73d36508196afaedaf1eeb21c52)
by [Unito](https://www.unito.io)
2025-11-18 14:48:09 -08:00
Christian Byrne
80b87c1277 ci: fix cache step in release version bump workflow (#6740)
Removed the unnecessary `cache: 'pnpm'` configuration from the workflow
since version bumping doesn't require dependency caching.

The workflow configured `cache: 'pnpm'` in the Node.js setup step but
never ran `pnpm install` to create the cache directories. The cache
action expects paths like `~/.pnpm-store` and `node_modules` to exist,
but since this workflow only runs `pnpm version` (which doesn't require
dependencies), those paths are never created.

Issue occurred here:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19478181571/job/55743250471

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6740-ci-fix-cache-step-in-release-version-bump-workflow-2af6d73d3650813b84a8e9d3272ad8fd)
by [Unito](https://www.unito.io)
2025-11-18 14:42:59 -08:00
Alexander Brown
00fa9b691b Fix: Simplify the widget state logic (#6741)
## Summary

Fixes the case where a value is updated in the graph but the result
doesn't reflect on the widget representation on the relevant node.

## Changes

- **What**: Uses vanilla Vue utilities instead of a special utility
- **What**: Fewer places where state could be desynced.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6741-Fix-WIP-Simplify-the-widget-state-logic-2af6d73d36508160b729db50608a2ea9)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 14:32:22 -08:00
Simula_r
0cff8eb357 fix: arbitrary styles, min size <= content, ensure layout calc, trunc… (#6731)
## Summary

### Problem:
After [vue node compacting
PR](https://github.com/Comfy-Org/ComfyUI_frontend/pull/6687) the white
space within the node has been greatly reduced, lowering the min
intrinsic size, thus allowing us to reduce the amount we need to scale
up via ensureCorrectLayoutScale(), therefore increasing readability of
nodes. Great!

However, a side effect of reducing the scale factor means nodes with
larger min content will not be scaled up enough causing nodes to be too
large in many cases.

For example, if the min intrinsic width is very long due to input
length:
<img width="807" height="519" alt="image"
src="https://github.com/user-attachments/assets/a6ea3852-bed5-49b2-b10e-c2e65c6450b2"
/>

### Solution:
Allow for nodes to be resized less than their intrinsic min width. And
truncate widget inputs like many other node UIs do.

IMPORTANT: when a node is added via search or other, it will still get a
min size based on its intrinsic content it just wont be the min width!
So best of both worlds.

<img width="670" height="551" alt="image"
src="https://github.com/user-attachments/assets/f4f5ec8c-037e-472f-a5a1-d8a59a87c0b0"
/>


this means we choose a default min width and clamp resize to it. This
also means we have to remove the arbitrary min width values that were
sprinkled around the vue node widgets. They are not needed because
instead of min width, they can take up full width and inherit the sizing
from the node min width! This makes nodes like little browser windows
and widgets are just responsive elements with in. Much more natural imo.

### Bonus
- Set ensureCorrectLayouScale() to scale factor of 1.2 which means vue
nodes are now only being set 20% bigger than LG. That covers for the
height difference we cant change!
- Fix ensureCorrectLayouScale() to offset y position for groups / better
alignment
- Get rid of arbitrary inflexible min width like min-[417px] which
shouldnt have been used the first place
- Make Select and Input overlay portals width set to their content


## Changes

**What**: 
- Node resizing behavior
- Node widget min width
- Widget input and slot truncation
- Misc arbitrary styling that should have been fluid

## Screenshots (if applicable)


https://github.com/user-attachments/assets/3ea4b8fe-565a-47f7-b3ab-6cef56cecde5


https://github.com/user-attachments/assets/2fe1e1a0-a9dc-4000-b865-ce2d8c7f3606


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6731-fix-arbitrary-styles-min-size-content-ensure-layout-calc-trunc-2af6d73d365081eab507c2f1638a4194)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 13:52:23 -07:00
396 changed files with 20117 additions and 12449 deletions

View File

@@ -1,5 +1,5 @@
# Description: When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Electron API Types'
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
workflow_dispatch:

View File

@@ -1,5 +1,5 @@
# Description: When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Manager API Types'
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
# Manual trigger

View File

@@ -1,5 +1,5 @@
# Description: When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Registry API Types'
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
# Manual trigger

View File

@@ -1,5 +1,5 @@
# Description: Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq
name: "CI: JSON Validation"
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Linting and code formatting validation for pull requests
name: "CI: Lint Format"
description: "Linting and code formatting validation for pull requests"
on:
pull_request:

View File

@@ -1,5 +1,5 @@
# Description: Validates Python code in tools/devtools directory
name: "CI: Python Validation"
description: "Validates Python code in tools/devtools directory"
on:
pull_request:

View File

@@ -1,5 +1,5 @@
# Description: Deploys test results from forked PRs (forks can't access deployment secrets)
name: "CI: Tests E2E (Deploy for Forks)"
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
on:
workflow_run:

View File

@@ -1,5 +1,5 @@
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
name: "CI: Tests E2E"
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Deploys Storybook previews from forked PRs (forks can't access deployment secrets)
name: "CI: Tests Storybook (Deploy for Forks)"
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
on:
workflow_run:

View File

@@ -1,10 +1,9 @@
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
name: "CI: Tests Storybook"
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
on:
workflow_dispatch: # Allow manual triggering
pull_request:
branches: [main]
jobs:
# Post starting comment for non-forked PRs

View File

@@ -1,5 +1,5 @@
# Description: Unit and component testing with Vitest
name: "CI: Tests Unit"
description: "Unit and component testing with Vitest"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Validates YAML syntax and style using yamllint with relaxed rules
name: "CI: YAML Validation"
description: "Validates YAML syntax and style using yamllint with relaxed rules"
on:
push:

View File

@@ -0,0 +1,69 @@
---
name: Cloud Backport Tag
on:
pull_request:
types: ['closed']
branches: [cloud/*]
jobs:
create-tag:
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'backport')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout merge commit
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
- name: Create tag for cloud backport
id: tag
run: |
set -euo pipefail
BRANCH="${{ github.event.pull_request.base.ref }}"
if [[ ! "$BRANCH" =~ ^cloud/([0-9]+)\.([0-9]+)$ ]]; then
echo "::error::Base branch '$BRANCH' is not a cloud/x.y branch"
exit 1
fi
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
VERSION=$(node -p "require('./package.json').version")
if [[ "$VERSION" =~ ^${MAJOR}\.${MINOR}\.([0-9]+)(-.+)?$ ]]; then
PATCH="${BASH_REMATCH[1]}"
SUFFIX="${BASH_REMATCH[2]:-}"
else
echo "::error::Version '${VERSION}' does not match cloud branch '${BRANCH}'"
exit 1
fi
TAG="cloud/v${VERSION}"
if git ls-remote --tags origin "${TAG}" | grep -Fq "refs/tags/${TAG}"; then
echo "::notice::Tag ${TAG} already exists; skipping"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
exit 0
fi
git tag "${TAG}" "${{ github.event.pull_request.merge_commit_sha }}"
git push origin "${TAG}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
{
echo "Created tag: ${TAG}"
echo "Branch: ${BRANCH}"
echo "Version: ${VERSION}"
echo "Commit: ${{ github.event.pull_request.merge_commit_sha }}"
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -1,5 +1,5 @@
# Description: Generates and updates translations for core ComfyUI components using OpenAI
name: "i18n: Update Core"
description: "Generates and updates translations for core ComfyUI components using OpenAI"
on:
# Manual dispatch for urgent translation updates

View File

@@ -78,8 +78,7 @@ jobs:
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
else
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
LABELS=$(echo "$LABELS" | jq -r '.[].name')
LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
fi
add_target() {
@@ -237,8 +236,8 @@ jobs:
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_TITLE="${{ github.event.pull_request.title }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
fi
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
@@ -327,8 +326,8 @@ jobs:
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
else
PR_TITLE="${{ github.event.pull_request.title }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
fi
for backport in ${{ steps.backport.outputs.success }}; do
@@ -365,9 +364,9 @@ jobs:
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
PR_NUMBER=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
fi
for failure in ${{ steps.backport.outputs.failed }}; do

View File

@@ -1,5 +1,5 @@
# Description: AI-powered code review triggered by adding the 'claude-review' label to a PR
name: "PR: Claude Review"
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
permissions:
contents: read

View File

@@ -148,10 +148,10 @@ jobs:
done
{
echo "results<<'EOF'"
echo "results<<EOF"
cat "$RESULTS_FILE"
echo "EOF"
} >> $GITHUB_OUTPUT
} >> "$GITHUB_OUTPUT"
- name: Ensure release labels
if: steps.check_version.outputs.is_minor_bump == 'true'

View File

@@ -1,5 +1,5 @@
# Description: Manual workflow to increment package version with semantic versioning support
name: "Release: Version Bump"
description: "Manual workflow to increment package version with semantic versioning support"
on:
workflow_dispatch:
@@ -59,7 +59,6 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'pnpm'
- name: Bump version
id: bump-version

View File

@@ -0,0 +1,292 @@
# Automated weekly workflow to bump ComfyUI frontend RC releases
name: "Release: Weekly ComfyUI"
on:
# Schedule for Monday at 12:00 PM PST (20:00 UTC)
schedule:
- cron: '0 20 * * 1'
# Allow manual triggering
workflow_dispatch:
inputs:
comfyui_fork:
description: 'ComfyUI fork to use for PR (e.g., Comfy-Org/ComfyUI)'
required: false
default: 'Comfy-Org/ComfyUI'
type: string
jobs:
resolve-version:
runs-on: ubuntu-latest
outputs:
current_version: ${{ steps.resolve.outputs.current_version }}
target_version: ${{ steps.resolve.outputs.target_version }}
target_minor: ${{ steps.resolve.outputs.target_minor }}
target_branch: ${{ steps.resolve.outputs.target_branch }}
needs_release: ${{ steps.resolve.outputs.needs_release }}
diff_url: ${{ steps.resolve.outputs.diff_url }}
latest_patch_tag: ${{ steps.resolve.outputs.latest_patch_tag }}
steps:
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v5
with:
fetch-depth: 0
path: frontend
- name: Checkout ComfyUI (sparse)
uses: actions/checkout@v5
with:
repository: comfyanonymous/ComfyUI
sparse-checkout: |
requirements.txt
path: comfyui
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Resolve release information
id: resolve
working-directory: frontend
run: |
set -euo pipefail
# Run the resolver script
if ! RESULT=$(tsx scripts/cicd/resolve-comfyui-release.ts ../comfyui .); then
echo "Failed to resolve release information"
exit 1
fi
echo "Resolver output:"
echo "$RESULT"
# Validate JSON output
if ! echo "$RESULT" | jq empty 2>/dev/null; then
echo "Invalid JSON output from resolver"
exit 1
fi
# Parse JSON output and set outputs
echo "current_version=$(echo "$RESULT" | jq -r '.current_version')" >> $GITHUB_OUTPUT
echo "target_version=$(echo "$RESULT" | jq -r '.target_version')" >> $GITHUB_OUTPUT
echo "target_minor=$(echo "$RESULT" | jq -r '.target_minor')" >> $GITHUB_OUTPUT
echo "target_branch=$(echo "$RESULT" | jq -r '.target_branch')" >> $GITHUB_OUTPUT
echo "needs_release=$(echo "$RESULT" | jq -r '.needs_release')" >> $GITHUB_OUTPUT
echo "diff_url=$(echo "$RESULT" | jq -r '.diff_url')" >> $GITHUB_OUTPUT
echo "latest_patch_tag=$(echo "$RESULT" | jq -r '.latest_patch_tag')" >> $GITHUB_OUTPUT
- name: Summary
run: |
echo "## Release Information" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Current version: ${{ steps.resolve.outputs.current_version }}" >> $GITHUB_STEP_SUMMARY
echo "- Target version: ${{ steps.resolve.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
echo "- Target branch: ${{ steps.resolve.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
echo "- Needs release: ${{ steps.resolve.outputs.needs_release }}" >> $GITHUB_STEP_SUMMARY
echo "- Diff: [${{ steps.resolve.outputs.current_version }}...${{ steps.resolve.outputs.target_version }}](${{ steps.resolve.outputs.diff_url }})" >> $GITHUB_STEP_SUMMARY
trigger-release-if-needed:
needs: resolve-version
if: needs.resolve-version.outputs.needs_release == 'true'
runs-on: ubuntu-latest
steps:
- name: Trigger release workflow
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
run: |
set -euo pipefail
echo "Triggering release workflow for branch ${{ needs.resolve-version.outputs.target_branch }}"
# Trigger the release-version-bump workflow
if ! gh workflow run release-version-bump.yaml \
--repo Comfy-Org/ComfyUI_frontend \
--ref main \
--field version_type=patch \
--field branch=${{ needs.resolve-version.outputs.target_branch }}; then
echo "Failed to trigger release workflow"
exit 1
fi
echo "Release workflow triggered successfully for ${{ needs.resolve-version.outputs.target_branch }}"
- name: Summary
run: |
echo "## Release Workflow Triggered" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Branch: ${{ needs.resolve-version.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
echo "- Target version: ${{ needs.resolve-version.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
echo "- [View workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)" >> $GITHUB_STEP_SUMMARY
create-comfyui-pr:
needs: [resolve-version, trigger-release-if-needed]
if: always() && needs.resolve-version.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout ComfyUI fork
uses: actions/checkout@v5
with:
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
token: ${{ secrets.PR_GH_TOKEN }}
path: comfyui
- name: Sync with upstream
working-directory: comfyui
run: |
set -euo pipefail
# Fetch latest upstream to base our branch on fresh code
# Note: This only affects the local checkout, NOT the fork's master branch
# We only push the automation branch, leaving the fork's master untouched
echo "Fetching upstream master..."
if ! git fetch https://github.com/comfyanonymous/ComfyUI.git master; then
echo "Failed to fetch upstream master"
exit 1
fi
echo "Checking out upstream master..."
if ! git checkout FETCH_HEAD; then
echo "Failed to checkout FETCH_HEAD"
exit 1
fi
echo "Successfully synced with upstream master"
- name: Update requirements.txt
working-directory: comfyui
run: |
set -euo pipefail
TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}"
echo "Updating comfyui-frontend-package to ${TARGET_VERSION}"
# Update the comfyui-frontend-package version (POSIX-compatible)
sed -i.bak "s/comfyui-frontend-package==[0-9.][0-9.]*/comfyui-frontend-package==${TARGET_VERSION}/" requirements.txt
rm requirements.txt.bak
# Verify the change was made
if ! grep -q "comfyui-frontend-package==${TARGET_VERSION}" requirements.txt; then
echo "Failed to update requirements.txt"
exit 1
fi
echo "Updated requirements.txt:"
grep comfyui-frontend-package requirements.txt
- name: Build PR description
id: pr-body
run: |
BODY=$(cat <<'EOF'
Bumps frontend to ${{ needs.resolve-version.outputs.target_version }}
Test quickly:
```bash
python main.py --front-end-version Comfy-Org/ComfyUI_frontend@${{ needs.resolve-version.outputs.target_version }}
```
- Diff: [v${{ needs.resolve-version.outputs.current_version }}...v${{ needs.resolve-version.outputs.target_version }}](${{ needs.resolve-version.outputs.diff_url }})
- PyPI: https://pypi.org/project/comfyui-frontend-package/${{ needs.resolve-version.outputs.target_version }}/
- npm: https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/${{ needs.resolve-version.outputs.target_version }}
EOF
)
# Add release PR note if release was triggered
if [ "${{ needs.resolve-version.outputs.needs_release }}" = "true" ]; then
RELEASE_NOTE="⚠️ **Release PR must be merged first** - check [release workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)"
BODY=$''"${RELEASE_NOTE}"$'\n\n'"${BODY}"
fi
# Save to file for later use
printf '%s\n' "$BODY" > pr-body.txt
cat pr-body.txt
- name: Create PR to ComfyUI
working-directory: comfyui
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
COMFYUI_FORK: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
run: |
set -euo pipefail
# Extract fork owner from repository name
FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1)
echo "Creating PR from ${COMFYUI_FORK} to comfyanonymous/ComfyUI"
# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Create/update branch (reuse same branch name each week)
BRANCH="automation/comfyui-frontend-bump"
git checkout -B "$BRANCH"
git add requirements.txt
if ! git diff --cached --quiet; then
git commit -m "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}"
else
echo "No changes to commit"
exit 0
fi
# Force push to fork (overwrites previous week's branch)
# Note: This intentionally destroys branch history to maintain a single PR
# Any review comments or manual commits will need to be re-applied
if ! git push -f origin "$BRANCH"; then
echo "Failed to push branch to fork"
exit 1
fi
# Create draft PR from fork to upstream
PR_BODY=$(cat ../pr-body.txt)
# Try to create PR, ignore error if it already exists
if ! gh pr create \
--repo comfyanonymous/ComfyUI \
--head "${FORK_OWNER}:${BRANCH}" \
--base master \
--title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \
--body "$PR_BODY" \
--draft 2>&1; then
# Check if PR already exists
set +e
EXISTING_PR=$(gh pr list --repo comfyanonymous/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1)
PR_LIST_EXIT=$?
set -e
if [ $PR_LIST_EXIT -ne 0 ]; then
echo "Failed to check for existing PR: $EXISTING_PR"
exit 1
fi
if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
echo "PR already exists (#${EXISTING_PR}), updating branch will update the PR"
else
echo "Failed to create PR and no existing PR found"
exit 1
fi
fi
- name: Summary
run: |
echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Draft PR created in comfyanonymous/ComfyUI" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### PR Body:" >> $GITHUB_STEP_SUMMARY
cat pr-body.txt >> $GITHUB_STEP_SUMMARY

View File

@@ -1,5 +1,5 @@
# Description: Automated weekly documentation accuracy check and update via Claude
name: "Weekly Documentation Check"
description: "Automated weekly documentation accuracy check and update via Claude"
permissions:
contents: write

View File

@@ -9,7 +9,7 @@ module.exports = defineConfig({
entry: 'src/locales/en',
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR'],
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.

View File

@@ -38,6 +38,7 @@
"typescript/no-redundant-type-constituents": "off",
"typescript/restrict-template-expressions": "off",
"typescript/unbound-method": "off",
"typescript/no-floating-promises": "error"
"typescript/no-floating-promises": "error",
"vue/no-import-compiler-macros": "error"
}
}

View File

@@ -64,7 +64,6 @@ const config: StorybookConfig = {
deep: true,
extensions: ['vue']
})
// Note: Explicitly NOT including generateImportMapPlugin to avoid externalization
],
server: {
allowedHosts: true

View File

@@ -1,8 +1,11 @@
# Global Ownership
* @Comfy-org/comfy_frontend_devs
# Desktop/Electron
/apps/desktop-ui/ @webfiltered
/src/stores/electronDownloadStore.ts @webfiltered
/src/extensions/core/electronAdapter.ts @webfiltered
/vite.electron.config.mts @webfiltered
/apps/desktop-ui/ @benceruleanlu
/src/stores/electronDownloadStore.ts @benceruleanlu
/src/extensions/core/electronAdapter.ts @benceruleanlu
/vite.electron.config.mts @benceruleanlu
# Common UI Components
/src/components/chip/ @viva-jinyi
@@ -31,10 +34,7 @@
/src/components/graph/selectionToolbox/ @Myestery
# Minimap
/src/renderer/extensions/minimap/ @jtydhr88
# Assets
/src/platform/assets/ @arjansingh
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery
# Workflow Templates
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
@@ -53,11 +53,12 @@
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
# Translations
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
/src/locales/pt-BR/ @JonatanAtila @Yorha4D @KarryCharon @shinshin86
# LLM Instructions (blank on purpose)
.claude/
.cursor/
.cursorrules
**/AGENTS.md
**/CLAUDE.md
**/CLAUDE.md

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/desktop-ui",
"version": "0.0.3",
"version": "0.0.4",
"type": "module",
"nx": {
"tags": [
@@ -91,7 +91,7 @@
"build-storybook": "storybook build -o dist/storybook"
},
"dependencies": {
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/comfyui-electron-types": "catalog:",
"@comfyorg/shared-frontend-utils": "workspace:*",
"@primevue/core": "catalog:",
"@primevue/themes": "catalog:",

View File

@@ -59,7 +59,8 @@ const LOCALES = [
['fr', 'Français'],
['es', 'Español'],
['ar', 'عربي'],
['tr', 'Türkçe']
['tr', 'Türkçe'],
['pt-BR', 'Português (BR)']
] as const satisfies ReadonlyArray<[string, string]>
type SupportedLocale = (typeof LOCALES)[number][0]

View File

@@ -22,7 +22,11 @@
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
{{ title }}
</h1>
<p v-if="statusText" class="text-lg text-neutral-400">
<p
v-if="statusText"
class="text-lg text-neutral-400"
data-testid="startup-status-text"
>
{{ statusText }}
</p>
</div>

View File

@@ -115,19 +115,18 @@ import Button from 'primevue/button'
import Divider from 'primevue/divider'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { type ModelRef, computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import type { ModelRef } from 'vue'
import { useI18n } from 'vue-i18n'
import MigrationPicker from '@/components/install/MigrationPicker.vue'
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
import {
PYPI_MIRROR,
PYTHON_MIRROR,
type UVMirror
} from '@/constants/uvMirrors'
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/uvMirrors'
import type { UVMirror } from '@/constants/uvMirrors'
import { electronAPI } from '@/utils/envUtil'
import { ValidationState } from '@/utils/validationUtil'
import MigrationPicker from './MigrationPicker.vue'
import MirrorItem from './mirror/MirrorItem.vue'
const { t } = useI18n()
const installPath = defineModel<string>('installPath', { required: true })
@@ -229,6 +228,10 @@ const validatePath = async (path: string | undefined) => {
}
if (validation.parentMissing) errors.push(t('install.parentMissing'))
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
if (validation.isInsideAppInstallDir)
errors.push(t('install.insideAppInstallDir'))
if (validation.isInsideUpdaterCache)
errors.push(t('install.insideUpdaterCache'))
if (validation.error)
errors.push(`${t('install.unhandledError')}: ${validation.error}`)

View File

@@ -16,7 +16,8 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
execute: async () => await electron.setBasePath(),
name: 'Base path',
shortDescription: 'Change the application base path.',
errorDescription: 'Unable to open the base path. Please select a new one.',
errorDescription:
'The current base path is invalid or unsafe. Please select a new location.',
description:
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
isInstallationFix: true,

View File

@@ -40,7 +40,8 @@ const localeLoaders: Record<
ru: () => import('@frontend-locales/ru/main.json'),
tr: () => import('@frontend-locales/tr/main.json'),
zh: () => import('@frontend-locales/zh/main.json'),
'zh-TW': () => import('@frontend-locales/zh-TW/main.json')
'zh-TW': () => import('@frontend-locales/zh-TW/main.json'),
'pt-BR': () => import('@frontend-locales/pt-BR/main.json')
}
const nodeDefsLoaders: Record<
@@ -55,7 +56,8 @@ const nodeDefsLoaders: Record<
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json')
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json'),
'pt-BR': () => import('@frontend-locales/pt-BR/nodeDefs.json')
}
const commandsLoaders: Record<
@@ -70,7 +72,8 @@ const commandsLoaders: Record<
ru: () => import('@frontend-locales/ru/commands.json'),
tr: () => import('@frontend-locales/tr/commands.json'),
zh: () => import('@frontend-locales/zh/commands.json'),
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json')
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json'),
'pt-BR': () => import('@frontend-locales/pt-BR/commands.json')
}
const settingsLoaders: Record<
@@ -85,7 +88,8 @@ const settingsLoaders: Record<
ru: () => import('@frontend-locales/ru/settings.json'),
tr: () => import('@frontend-locales/tr/settings.json'),
zh: () => import('@frontend-locales/zh/settings.json'),
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json')
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json'),
'pt-BR': () => import('@frontend-locales/pt-BR/settings.json')
}
// Track which locales have been loaded

View File

@@ -85,6 +85,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
const electron = electronAPI()
// Reactive state
const lastUpdate = ref<InstallValidation | null>(null)
const isRefreshing = ref(false)
const isRunningTerminalCommand = computed(() =>
tasks.value
@@ -97,6 +98,13 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
.some((task) => getRunner(task)?.executing)
)
const unsafeBasePath = computed(
() => lastUpdate.value?.unsafeBasePath === true
)
const unsafeBasePathReason = computed(
() => lastUpdate.value?.unsafeBasePathReason
)
// Task list
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
@@ -123,6 +131,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
* @param validationUpdate Update details passed in by electron
*/
const processUpdate = (validationUpdate: InstallValidation) => {
lastUpdate.value = validationUpdate
const update = validationUpdate as IndexedUpdate
isRefreshing.value = true
@@ -155,7 +164,11 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
}
const execute = async (task: MaintenanceTask) => {
return getRunner(task).execute(task)
const success = await getRunner(task).execute(task)
if (success && task.isInstallationFix) {
await refreshDesktopTasks()
}
return success
}
return {
@@ -163,6 +176,8 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
isRefreshing,
isRunningTerminalCommand,
isRunningInstallationFix,
unsafeBasePath,
unsafeBasePathReason,
execute,
getRunner,
processUpdate,

View File

@@ -0,0 +1,159 @@
// eslint-disable-next-line storybook/no-renderer-packages
import type { Meta, StoryObj } from '@storybook/vue3'
import { defineAsyncComponent } from 'vue'
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
type ValidationState = {
inProgress: boolean
installState: string
basePath?: ValidationIssueState
unsafeBasePath: boolean
unsafeBasePathReason: UnsafeReason
venvDirectory?: ValidationIssueState
pythonInterpreter?: ValidationIssueState
pythonPackages?: ValidationIssueState
uv?: ValidationIssueState
git?: ValidationIssueState
vcRedist?: ValidationIssueState
upgradePackages?: ValidationIssueState
}
const validationState: ValidationState = {
inProgress: false,
installState: 'installed',
basePath: 'OK',
unsafeBasePath: false,
unsafeBasePathReason: null,
venvDirectory: 'OK',
pythonInterpreter: 'OK',
pythonPackages: 'OK',
uv: 'OK',
git: 'OK',
vcRedist: 'OK',
upgradePackages: 'OK'
}
const createMockElectronAPI = () => {
const logListeners: Array<(message: string) => void> = []
const getValidationUpdate = () => ({
...validationState
})
return {
getPlatform: () => 'darwin',
changeTheme: (_theme: unknown) => {},
onLogMessage: (listener: (message: string) => void) => {
logListeners.push(listener)
},
showContextMenu: (_options: unknown) => {},
Events: {
trackEvent: (_eventName: string, _data?: unknown) => {}
},
Validation: {
onUpdate: (_callback: (update: unknown) => void) => {},
async getStatus() {
return getValidationUpdate()
},
async validateInstallation(callback: (update: unknown) => void) {
callback(getValidationUpdate())
},
async complete() {
// Only allow completion when the base path is safe
return !validationState.unsafeBasePath
},
dispose: () => {}
},
setBasePath: () => Promise.resolve(true),
reinstall: () => Promise.resolve(),
uv: {
installRequirements: () => Promise.resolve(),
clearCache: () => Promise.resolve(),
resetVenv: () => Promise.resolve()
}
}
}
const ensureElectronAPI = () => {
const globalWindow = window as unknown as { electronAPI?: unknown }
if (!globalWindow.electronAPI) {
globalWindow.electronAPI = createMockElectronAPI()
}
return globalWindow.electronAPI
}
const MaintenanceView = defineAsyncComponent(async () => {
ensureElectronAPI()
const module = await import('./MaintenanceView.vue')
return module.default
})
const meta: Meta<typeof MaintenanceView> = {
title: 'Desktop/Views/MaintenanceView',
component: MaintenanceView,
parameters: {
layout: 'fullscreen',
backgrounds: {
default: 'dark',
values: [
{ name: 'dark', value: '#0a0a0a' },
{ name: 'neutral-900', value: '#171717' },
{ name: 'neutral-950', value: '#0a0a0a' }
]
}
}
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
name: 'All tasks OK',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'OK'
validationState.unsafeBasePath = false
validationState.unsafeBasePathReason = null
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}
export const UnsafeBasePathOneDrive: Story = {
name: 'Unsafe base path (OneDrive)',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'error'
validationState.unsafeBasePath = true
validationState.unsafeBasePathReason = 'oneDrive'
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}

View File

@@ -47,6 +47,28 @@
</div>
</div>
<!-- Unsafe migration warning -->
<div v-if="taskStore.unsafeBasePath" class="my-4">
<p class="flex items-start gap-3 text-neutral-300">
<Tag
icon="pi pi-exclamation-triangle"
severity="warn"
:value="t('icon.exclamation-triangle')"
/>
<span>
<strong class="block mb-1">
{{ t('maintenance.unsafeMigration.title') }}
</strong>
<span class="block mb-1">
{{ unsafeReasonText }}
</span>
<span class="block text-sm text-neutral-400">
{{ t('maintenance.unsafeMigration.action') }}
</span>
</span>
</p>
</div>
<!-- Tasks -->
<TaskListPanel
class="border-neutral-700 border-solid border-x-0 border-y"
@@ -89,10 +111,10 @@
import { PrimeIcons } from '@primevue/core/api'
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import Tag from 'primevue/tag'
import Toast from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import RefreshButton from '@/components/common/RefreshButton.vue'
import StatusTag from '@/components/maintenance/StatusTag.vue'
@@ -139,6 +161,27 @@ const filterOptions = ref([
/** Filter binding; can be set to show all tasks, or only errors. */
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
const unsafeReasonText = computed(() => {
const reason = taskStore.unsafeBasePathReason
if (!reason) {
return t('maintenance.unsafeMigration.generic')
}
if (reason === 'appInstallDir') {
return t('maintenance.unsafeMigration.appInstallDir')
}
if (reason === 'updaterCache') {
return t('maintenance.unsafeMigration.updaterCache')
}
if (reason === 'oneDrive') {
return t('maintenance.unsafeMigration.oneDrive')
}
return t('maintenance.unsafeMigration.generic')
})
/** If valid, leave the validation window. */
const completeValidation = async () => {
const isValid = await electron.Validation.complete()

View File

@@ -85,11 +85,10 @@
</template>
<script setup lang="ts">
import {
InstallStage,
type InstallStageInfo,
type InstallStageName,
ProgressStatus
import { InstallStage, ProgressStatus } from '@comfyorg/comfyui-electron-types'
import type {
InstallStageInfo,
InstallStageName
} from '@comfyorg/comfyui-electron-types'
import type { Terminal } from '@xterm/xterm'
import Button from 'primevue/button'

View File

@@ -16,7 +16,6 @@ import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
import { SettingDialog } from './components/SettingDialog'
import {
NodeLibrarySidebarTab,
QueueSidebarTab,
WorkflowsSidebarTab
} from './components/SidebarTab'
import { Topbar } from './components/Topbar'
@@ -31,7 +30,6 @@ type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
class ComfyMenu {
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
private _workflowsTab: WorkflowsSidebarTab | null = null
private _queueTab: QueueSidebarTab | null = null
private _topbar: Topbar | null = null
public readonly sideToolbar: Locator
@@ -60,11 +58,6 @@ class ComfyMenu {
return this._workflowsTab
}
get queueTab() {
this._queueTab ??= new QueueSidebarTab(this.page)
return this._queueTab
}
get topbar() {
this._topbar ??= new Topbar(this.page)
return this._topbar
@@ -564,7 +557,7 @@ export class ComfyPage {
async dragAndDrop(source: Position, target: Position) {
await this.page.mouse.move(source.x, source.y)
await this.page.mouse.down()
await this.page.mouse.move(target.x, target.y)
await this.page.mouse.move(target.x, target.y, { steps: 100 })
await this.page.mouse.up()
await this.nextFrame()
}
@@ -1658,7 +1651,10 @@ export const comfyPageFixture = base.extend<{
// Set tutorial completed to true to avoid loading the tutorial workflow.
'Comfy.TutorialCompleted': true,
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
'Comfy.VueNodes.AutoScaleLayout': false
'Comfy.VueNodes.AutoScaleLayout': false,
// Disable toast warning about version compatibility, as they may or
// may not appear - depending on upstream ComfyUI dependencies
'Comfy.VersionCompatibility.DisableWarnings': true
})
} catch (e) {
console.error(e)

View File

@@ -65,7 +65,9 @@ export class VueNodeHelpers {
* Select a specific Vue node by ID
*/
async selectNode(nodeId: string): Promise<void> {
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
await this.page
.locator(`[data-node-id="${nodeId}"] .lg-node-header`)
.click()
}
/**
@@ -77,11 +79,13 @@ export class VueNodeHelpers {
// Select first node normally
await this.selectNode(nodeIds[0])
// Add additional nodes with Ctrl+click
// Add additional nodes with Ctrl+click on header
for (let i = 1; i < nodeIds.length; i++) {
await this.page.locator(`[data-node-id="${nodeIds[i]}"]`).click({
modifiers: ['Control']
})
await this.page
.locator(`[data-node-id="${nodeIds[i]}"] .lg-node-header`)
.click({
modifiers: ['Control']
})
}
}

View File

@@ -148,124 +148,3 @@ export class WorkflowsSidebarTab extends SidebarTab {
.click()
}
}
export class QueueSidebarTab extends SidebarTab {
constructor(public readonly page: Page) {
super(page, 'queue')
}
get root() {
return this.page.locator('.sidebar-content-container', { hasText: 'Queue' })
}
get tasks() {
return this.root.locator('[data-virtual-grid-item]')
}
get visibleTasks() {
return this.tasks.locator('visible=true')
}
get clearButton() {
return this.root.locator('.clear-all-button')
}
get collapseTasksButton() {
return this.getToggleExpandButton(false)
}
get expandTasksButton() {
return this.getToggleExpandButton(true)
}
get noResultsPlaceholder() {
return this.root.locator('.no-results-placeholder')
}
get galleryImage() {
return this.page.locator('.galleria-image')
}
private getToggleExpandButton(isExpanded: boolean) {
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
}
async open() {
await super.open()
return this.root.waitFor({ state: 'visible' })
}
async close() {
await super.close()
await this.root.waitFor({ state: 'hidden' })
}
async expandTasks() {
await this.expandTasksButton.click()
await this.collapseTasksButton.waitFor({ state: 'visible' })
}
async collapseTasks() {
await this.collapseTasksButton.click()
await this.expandTasksButton.waitFor({ state: 'visible' })
}
async waitForTasks() {
return Promise.all([
this.tasks.first().waitFor({ state: 'visible' }),
this.tasks.last().waitFor({ state: 'visible' })
])
}
async scrollTasks(direction: 'up' | 'down') {
const scrollToEl =
direction === 'up' ? this.tasks.last() : this.tasks.first()
await scrollToEl.scrollIntoViewIfNeeded()
await this.waitForTasks()
}
async clearTasks() {
await this.clearButton.click()
const confirmButton = this.page.getByLabel('Delete')
await confirmButton.click()
await this.noResultsPlaceholder.waitFor({ state: 'visible' })
}
/** Set the width of the tab (out of 100). Must call before opening the tab */
async setTabWidth(width: number) {
if (width < 0 || width > 100) {
throw new Error('Width must be between 0 and 100')
}
return this.page.evaluate((width) => {
localStorage.setItem('queue', JSON.stringify([width, 100 - width]))
}, width)
}
getTaskPreviewButton(taskIndex: number) {
return this.tasks.nth(taskIndex).getByRole('button')
}
async openTaskPreview(taskIndex: number) {
const previewButton = this.getTaskPreviewButton(taskIndex)
await previewButton.click()
return this.galleryImage.waitFor({ state: 'visible' })
}
getGalleryImage(imageFilename: string) {
return this.galleryImage.and(this.page.getByAltText(imageFilename))
}
getTaskImage(imageFilename: string) {
return this.tasks.getByAltText(imageFilename)
}
/** Trigger the queue store and tasks to update */
async triggerTasksUpdate() {
await this.page.evaluate(() => {
window['app']['api'].dispatchCustomEvent('status', {
exec_info: { queue_remaining: 0 }
})
})
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -2,6 +2,7 @@ import {
comfyExpect as expect,
comfyPageFixture as test
} from '../fixtures/ComfyPage'
import { fitToViewInstant } from '../helpers/fitToView'
// TODO: there might be a better solution for this
// Helper function to pan canvas and select node
@@ -516,6 +517,7 @@ This is English documentation.
)
await comfyPage.loadWorkflow('default')
await fitToViewInstant(comfyPage)
// Select KSampler first
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1,210 +0,0 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe.skip('Queue sidebar', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
test('can display tasks', async ({ comfyPage }) => {
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
test('can display tasks after closing then opening', async ({
comfyPage
}) => {
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.close()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
test.describe('Virtual scroll', () => {
const layouts = [
{ description: 'Five columns layout', width: 95, rows: 3, cols: 5 },
{ description: 'Three columns layout', width: 55, rows: 3, cols: 3 },
{ description: 'Two columns layout', width: 40, rows: 3, cols: 2 }
]
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(50)
.setupRoutes()
})
layouts.forEach(({ description, width, rows, cols }) => {
const preRenderedRows = 1
const preRenderedTasks = preRenderedRows * cols * 2
const visibleTasks = rows * cols
const expectRenderLimit = visibleTasks + preRenderedTasks
test.describe(description, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.menu.queueTab.setTabWidth(width)
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
})
test('should not render items outside of view', async ({
comfyPage
}) => {
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
test('should teardown items after scrolling away', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.scrollTasks('down')
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
test('should re-render items after scrolling away then back', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.scrollTasks('down')
await comfyPage.menu.queueTab.scrollTasks('up')
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
})
})
})
test.describe('Expand tasks', () => {
test.beforeEach(async ({ comfyPage }) => {
// 2-item batch and 3-item batch -> 3 additional items when expanded
await comfyPage
.setupHistory()
.withTask(['example.webp', 'example.webp', 'example.webp'])
.withTask(['example.webp', 'example.webp'])
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
})
test('can expand tasks with multiple outputs', async ({ comfyPage }) => {
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
await comfyPage.menu.queueTab.expandTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
initialCount + 3
)
})
test('can collapse flat tasks', async ({ comfyPage }) => {
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
await comfyPage.menu.queueTab.expandTasks()
await comfyPage.menu.queueTab.collapseTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
initialCount
)
})
})
test.describe('Clear tasks', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(6)
.setupRoutes()
await comfyPage.menu.queueTab.open()
})
test('can clear all tasks', async ({ comfyPage }) => {
await comfyPage.menu.queueTab.clearTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(0)
expect(
await comfyPage.menu.queueTab.noResultsPlaceholder.isVisible()
).toBe(true)
})
test('can load new tasks after clearing all', async ({ comfyPage }) => {
await comfyPage.menu.queueTab.clearTasks()
await comfyPage.menu.queueTab.close()
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
})
test.describe('Gallery', () => {
const firstImage = 'example.webp'
const secondImage = 'image32x32.webp'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask([secondImage])
.withTask([firstImage])
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
await comfyPage.menu.queueTab.openTaskPreview(0)
})
test('displays gallery image after opening task preview', async ({
comfyPage
}) => {
await comfyPage.nextFrame()
await expect(
comfyPage.menu.queueTab.getGalleryImage(firstImage)
).toBeVisible()
})
test('maintains active gallery item when new tasks are added', async ({
comfyPage
}) => {
// Add a new task while the gallery is still open
const newImage = 'image64x64.webp'
comfyPage.setupHistory().withTask([newImage])
await comfyPage.menu.queueTab.triggerTasksUpdate()
await comfyPage.page.waitForTimeout(500)
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
await newTask.waitFor({ state: 'visible' })
// The active gallery item should still be the initial image
await expect(
comfyPage.menu.queueTab.getGalleryImage(firstImage)
).toBeVisible()
})
test.describe('Gallery navigation', () => {
const paths: {
description: string
path: ('Right' | 'Left')[]
end: string
}[] = [
{ description: 'Right', path: ['Right'], end: secondImage },
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
{ description: 'Left wrap', path: ['Left'], end: secondImage },
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
]
paths.forEach(({ description, path, end }) => {
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
for (const direction of path)
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
delay: 256
})
await comfyPage.nextFrame()
await expect(
comfyPage.menu.queueTab.getGalleryImage(end)
).toBeVisible()
})
})
})
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -6,6 +6,7 @@ import {
test.describe('Vue Nodes Zoom', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
await comfyPage.vueNodes.waitForNodes()
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,54 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../../fixtures/ComfyPage'
test.describe('Vue Node Resizing', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should resize node without position drift after selecting', async ({
comfyPage
}) => {
// Get a Vue node fixture
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
const initialBox = await node.boundingBox()
if (!initialBox) throw new Error('Node bounding box not found')
// Select the node first (this was causing the bug)
await node.header.click()
await comfyPage.page.waitForTimeout(100) // Brief pause after selection
// Get position after selection
const selectedBox = await node.boundingBox()
if (!selectedBox)
throw new Error('Node bounding box not found after select')
// Verify position unchanged after selection
expect(selectedBox.x).toBeCloseTo(initialBox.x, 1)
expect(selectedBox.y).toBeCloseTo(initialBox.y, 1)
// Now resize from bottom-right corner
const resizeStartX = selectedBox.x + selectedBox.width - 5
const resizeStartY = selectedBox.y + selectedBox.height - 5
await comfyPage.page.mouse.move(resizeStartX, resizeStartY)
await comfyPage.page.mouse.down()
await comfyPage.page.mouse.move(resizeStartX + 50, resizeStartY + 30)
await comfyPage.page.mouse.up()
// Get final position and size
const finalBox = await node.boundingBox()
if (!finalBox) throw new Error('Node bounding box not found after resize')
// Position should NOT have changed (the bug was position drift)
expect(finalBox.x).toBeCloseTo(initialBox.x, 1)
expect(finalBox.y).toBeCloseTo(initialBox.y, 1)
// Size should have increased
expect(finalBox.width).toBeGreaterThan(initialBox.width)
expect(finalBox.height).toBeGreaterThan(initialBox.height)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1,48 +0,0 @@
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 comboboxesInNodes = vueNodesContainer.getByRole('combobox')
await expect(textboxesInNodes.first()).toBeVisible()
await expect(comboboxesInNodes.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(comboboxesInNodes.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(comboboxesInNodes.first()).toBeVisible()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -1,154 +0,0 @@
import glob from 'fast-glob'
import fs from 'fs-extra'
import { dirname, join } from 'node:path'
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
interface ImportMapSource {
name: string
pattern: string | RegExp
entry: string
recursiveDependence?: boolean
override?: Record<string, Partial<ImportMapSource>>
}
const parseDeps = (root: string, pkg: string) => {
const pkgPath = join(root, 'node_modules', pkg, 'package.json')
if (fs.existsSync(pkgPath)) {
const content = fs.readFileSync(pkgPath, 'utf-8')
const pkg = JSON.parse(content)
return Object.keys(pkg.dependencies || {})
}
return []
}
/**
* Vite plugin that generates an import map for vendor chunks.
*
* This plugin creates a browser-compatible import map that maps module specifiers
* (like 'vue' or 'primevue') to their actual file locations in the build output.
* This improves module loading in modern browsers and enables better caching.
*
* The plugin:
* 1. Tracks vendor chunks during bundle generation
* 2. Creates mappings between module names and their file paths
* 3. Injects an import map script tag into the HTML head
* 4. Configures manual chunk splitting for vendor libraries
*
* @param vendorLibraries - An array of vendor libraries to split into separate chunks
* @returns {Plugin} A Vite plugin that generates and injects an import map
*/
export function generateImportMapPlugin(
importMapSources: ImportMapSource[]
): Plugin {
const importMapEntries: Record<string, string> = {}
const resolvedImportMapSources: Map<string, ImportMapSource> = new Map()
const assetDir = 'assets/lib'
let root: string
return {
name: 'generate-import-map-plugin',
// Configure manual chunks during the build process
configResolved(config) {
root = config.root
if (config.build) {
// Ensure rollupOptions exists
if (!config.build.rollupOptions) {
config.build.rollupOptions = {}
}
for (const source of importMapSources) {
resolvedImportMapSources.set(source.name, source)
if (source.recursiveDependence) {
const deps = parseDeps(root, source.name)
while (deps.length) {
const dep = deps.shift()!
const depSource = Object.assign({}, source, {
name: dep,
pattern: dep,
...source.override?.[dep]
})
resolvedImportMapSources.set(depSource.name, depSource)
const _deps = parseDeps(root, depSource.name)
deps.unshift(..._deps)
}
}
}
const external: (string | RegExp)[] = []
for (const [, source] of resolvedImportMapSources) {
external.push(source.pattern)
}
config.build.rollupOptions.external = external
}
},
generateBundle(_options) {
for (const [, source] of resolvedImportMapSources) {
if (source.entry) {
const moduleFile = join(source.name, source.entry)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
importMapEntries[source.name] =
'./' + normalizePath(join(assetDir, moduleFile))
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
if (source.recursiveDependence) {
const files = glob.sync(['**/*.{js,mjs}'], {
cwd: join(root, 'node_modules', source.name)
})
for (const file of files) {
const moduleFile = join(source.name, file)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
importMapEntries[normalizePath(join(source.name, dirname(file)))] =
'./' + normalizePath(join(assetDir, moduleFile))
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
}
}
},
transformIndexHtml(html) {
if (Object.keys(importMapEntries).length === 0) {
console.warn(
'[ImportMap Plugin] No vendor chunks found to create import map.'
)
return html
}
const importMap = {
imports: importMapEntries
}
const importMapTag: HtmlTagDescriptor = {
tag: 'script',
attrs: { type: 'importmap' },
children: JSON.stringify(importMap, null, 2),
injectTo: 'head'
}
return {
html,
tags: [importMapTag]
}
}
}
}

View File

@@ -1,2 +1 @@
export { comfyAPIPlugin } from './comfyAPIPlugin'
export { generateImportMapPlugin } from './generateImportMapPlugin'

View File

@@ -191,6 +191,19 @@ export default defineConfig([
'@intlify/vue-i18n/no-raw-text': [
'error',
{
attributes: {
'/.+/': [
'aria-label',
'aria-placeholder',
'aria-roledescription',
'aria-valuetext',
'label',
'placeholder',
'title',
'v-tooltip'
],
img: ['alt']
},
// Ignore strings that are:
// 1. Less than 2 characters
// 2. Only symbols/numbers/whitespace (no letters)
@@ -200,24 +213,27 @@ export default defineConfig([
ignoreNodes: ['md-icon', 'v-icon', 'pre', 'code', 'script', 'style'],
// Brand names and technical terms that shouldn't be translated
ignoreText: [
'ComfyUI',
'GitHub',
'OpenAI',
'API',
'URL',
'JSON',
'YAML',
'GPU',
'CPU',
'RAM',
'GB',
'MB',
'KB',
'ms',
'fps',
'px',
'App Data:',
'App Path:'
'App Path:',
'ComfyUI',
'CPU',
'fps',
'GB',
'GitHub',
'GPU',
'JSON',
'KB',
'LoRA',
'MB',
'ms',
'OpenAI',
'png',
'px',
'RAM',
'URL',
'YAML',
'1.2 MB'
]
}
]

View File

@@ -27,7 +27,7 @@ const config: KnipConfig = {
project: ['src/**/*.{js,ts}']
}
},
ignoreBinaries: ['python3'],
ignoreBinaries: ['python3', 'gh'],
ignoreDependencies: [
// Weird importmap things
'@iconify/json',

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.32.5",
"version": "1.34.3",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -75,6 +75,7 @@
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"@vue/test-utils": "catalog:",
"@webgpu/types": "catalog:",
"cross-env": "catalog:",
"eslint": "catalog:",
"eslint-config-prettier": "catalog:",
@@ -112,6 +113,7 @@
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"unplugin-icons": "catalog:",
"unplugin-typegpu": "catalog:",
"unplugin-vue-components": "catalog:",
"uuid": "^11.1.0",
"vite": "catalog:",
@@ -128,7 +130,7 @@
"dependencies": {
"@alloc/quick-lru": "catalog:",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/comfyui-electron-types": "catalog:",
"@comfyorg/design-system": "workspace:*",
"@comfyorg/registry-types": "workspace:*",
"@comfyorg/shared-frontend-utils": "workspace:*",
@@ -162,7 +164,6 @@
"es-toolkit": "^1.39.9",
"extendable-media-recorder": "^9.2.27",
"extendable-media-recorder-wav-encoder": "^7.0.129",
"fast-glob": "^3.3.3",
"firebase": "catalog:",
"fuse.js": "^7.0.0",
"glob": "^11.0.3",
@@ -176,6 +177,7 @@
"semver": "^7.7.2",
"three": "^0.170.0",
"tiptap-markdown": "^0.8.10",
"typegpu": "catalog:",
"vue": "catalog:",
"vue-i18n": "catalog:",
"vue-router": "catalog:",

View File

@@ -98,6 +98,9 @@
--color-comfy-menu-secondary: var(--comfy-menu-secondary-bg);
--color-interface-panel-job-progress-primary: var(--color-azure-300);
--color-interface-panel-job-progress-secondary: var(--color-alpha-azure-600-30);
--color-blue-selection: rgb(from var(--color-azure-600) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-800) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1);
@@ -191,7 +194,7 @@
--node-component-executing: var(--color-blue-500);
--node-component-header: var(--fg-color);
--node-component-header-icon: var(--color-ash-800);
--node-component-header-surface: var(--color-white);
--node-component-header-surface: var(--color-smoke-400);
--node-component-outline: var(--color-black);
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
--node-component-slot-dot-outline-opacity-mult: 1;
@@ -265,12 +268,13 @@
--palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color));
--modal-card-background: var(--secondary-background);
--modal-card-background-hovered: var(--secondary-background-hover);
--modal-card-border-highlighted: var(--secondary-background-selected);
--modal-card-button-surface: var(--color-smoke-300);
--modal-card-placeholder-background: var(--color-smoke-600);
--modal-card-tag-background: var(--color-smoke-200);
--modal-card-tag-background: var(--color-smoke-400);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-white);
}
.dark-theme {
@@ -330,6 +334,12 @@
--node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-azure-600);
/* Queue progress (dark theme) */
--color-interface-panel-job-progress-primary: var(--color-cobalt-800);
--color-interface-panel-job-progress-secondary: var(
--color-alpha-azure-600-30
);
--text-secondary: var(--color-slate-100);
--text-primary: var(--color-white);
@@ -368,9 +378,11 @@
--component-node-widget-background-highlighted: var(--color-graphite-400);
--modal-card-background: var(--secondary-background);
--modal-card-background-hovered: var(--secondary-background-hover);
--modal-card-border-highlighted: var(--color-ash-400);
--modal-card-button-surface: var(--color-charcoal-300);
--modal-card-placeholder-background: var(--secondary-background);
--modal-card-tag-background: var(--color-charcoal-200);
--modal-card-tag-background: var(--color-ash-800);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-charcoal-600);
@@ -386,12 +398,14 @@
--color-subscription-button-gradient: var(--subscription-button-gradient);
--color-modal-card-background: var(--modal-card-background);
--color-modal-card-background-hovered: var(--modal-card-background-hovered);
--color-modal-card-border-highlighted: var(--modal-card-border-highlighted);
--color-modal-card-button-surface: var(--modal-card-button-surface);
--color-modal-card-placeholder-background: var(--modal-card-placeholder-background);
--color-modal-card-tag-background: var(--modal-card-tag-background);
--color-modal-card-tag-foreground: var(--modal-card-tag-foreground);
--color-modal-panel-background: var(--modal-panel-background);
--color-dialog-surface: var(--dialog-surface);
--color-interface-menu-component-surface-hovered: var(
--interface-menu-component-surface-hovered
@@ -1176,24 +1190,19 @@ dialog::backdrop {
.litegraph.litecontextmenu,
.litegraph.litecontextmenu.dark {
z-index: 9999 !important;
background-color: var(--comfy-menu-bg) !important;
background-color: var(--comfy-menu-bg);
}
.litegraph.litecontextmenu
.litemenu-entry:hover:not(.disabled):not(.separator) {
background-color: var(--comfy-menu-hover-bg, var(--border-color)) !important;
color: var(--fg-color);
}
.litegraph.litecontextmenu .litemenu-entry.submenu,
.litegraph.litecontextmenu.dark .litemenu-entry.submenu {
background-color: var(--comfy-menu-bg) !important;
color: var(--input-text);
background-color: var(--comfy-menu-bg);
color: var(--fg-color);
}
.litegraph.litecontextmenu input {
background-color: var(--comfy-input-bg) !important;
color: var(--input-text) !important;
background-color: var(--comfy-input-bg);
color: var(--input-text);
}
.comfy-context-menu-filter {
@@ -1234,14 +1243,14 @@ dialog::backdrop {
.litegraph.litesearchbox {
z-index: 9999 !important;
background-color: var(--comfy-menu-bg) !important;
background-color: var(--comfy-menu-bg);
overflow: hidden;
display: block;
}
.litegraph.litesearchbox input,
.litegraph.litesearchbox select {
background-color: var(--comfy-input-bg) !important;
background-color: var(--comfy-input-bg);
color: var(--input-text);
}
@@ -1315,57 +1324,6 @@ audio.comfy-audio.empty-audio-widget {
will-change: transform;
}
/* START LOD specific styles */
/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */
.isLOD .lg-node {
box-shadow: none;
filter: none;
backdrop-filter: none;
text-shadow: none;
mask-image: none;
clip-path: none;
background-image: none;
text-rendering: optimizeSpeed;
border-radius: 0;
contain: layout style;
transition: none;
}
.isLOD .lg-node-header {
border-radius: 0;
pointer-events: none;
}
.isLOD .lg-node-widgets {
pointer-events: none;
}
.lod-toggle {
visibility: visible;
}
.isLOD .lod-toggle {
visibility: hidden;
}
.lod-fallback {
display: none;
}
.isLOD .lod-fallback {
display: block;
}
.isLOD .image-preview img {
image-rendering: pixelated;
}
.isLOD .slot-dot {
border-radius: 0;
}
/* END LOD specific styles */
/* ===================== Mask Editor Styles ===================== */
/* To be migrated to Tailwind later */
#maskEditor_brush {
@@ -1827,4 +1785,4 @@ audio.comfy-audio.empty-audio-widget {
.maskEditor_sidePanelLayerCheckbox {
margin-left: 15px;
}
/* ===================== End of Mask Editor Styles ===================== */
/* ===================== End of Mask Editor Styles ===================== */

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.33398 1.33337V4.00004C9.33398 4.35366 9.47446 4.6928 9.72451 4.94285C9.97456 5.1929 10.3137 5.33337 10.6673 5.33337H13.334M2.66732 4.66671V2.66671C2.66732 2.31309 2.80779 1.97395 3.05784 1.7239C3.30789 1.47385 3.64703 1.33337 4.00065 1.33337H10.0006L13.334 4.66671V13.3334C13.334 13.687 13.1935 14.0261 12.9435 14.2762C12.6934 14.5262 12.3543 14.6667 12.0006 14.6667L4.04264 14.666C3.77927 14.7004 3.51166 14.6552 3.2741 14.5365C3.03655 14.4177 2.83988 14.2307 2.70931 13.9994M3.33398 7.33337L1.33398 9.33337M1.33398 9.33337L3.33398 11.3334M1.33398 9.33337H8.00065" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 771 B

View File

@@ -75,6 +75,17 @@ export function formatSize(value?: number) {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
}
/**
* Formats a commit hash by truncating long (40-char) hashes to 7 chars.
* Returns the original string if not a valid full commit hash.
*/
export function formatCommitHash(value: string): string {
if (/^[a-f0-9]{40}$/i.test(value)) {
return value.slice(0, 7)
}
return value
}
/**
* Returns various filename components.
* Example:

150
pnpm-lock.yaml generated
View File

@@ -9,18 +9,15 @@ catalogs:
'@alloc/quick-lru':
specifier: ^5.2.0
version: 5.2.0
'@comfyorg/comfyui-electron-types':
specifier: 0.5.5
version: 0.5.5
'@eslint/js':
specifier: ^9.35.0
version: 9.35.0
'@iconify-json/lucide':
specifier: ^1.1.178
version: 1.2.66
'@iconify/json':
specifier: ^2.2.380
version: 2.2.380
'@iconify/tailwind':
specifier: ^1.1.3
version: 1.2.0
'@intlify/eslint-plugin-vue-i18n':
specifier: ^4.1.0
version: 4.1.0
@@ -123,6 +120,9 @@ catalogs:
'@vueuse/integrations':
specifier: ^13.9.0
version: 13.9.0
'@webgpu/types':
specifier: ^0.1.66
version: 0.1.66
algoliasearch:
specifier: ^5.21.0
version: 5.21.0
@@ -243,6 +243,9 @@ catalogs:
tw-animate-css:
specifier: ^1.3.8
version: 1.3.8
typegpu:
specifier: ^0.8.2
version: 0.8.2
typescript:
specifier: ^5.9.2
version: 5.9.2
@@ -252,6 +255,9 @@ catalogs:
unplugin-icons:
specifier: ^0.22.0
version: 0.22.0
unplugin-typegpu:
specifier: 0.8.0
version: 0.8.0
unplugin-vue-components:
specifier: ^0.28.0
version: 0.28.0
@@ -318,8 +324,8 @@ importers:
specifier: ^1.3.1
version: 1.3.1
'@comfyorg/comfyui-electron-types':
specifier: 0.4.73-0
version: 0.4.73-0
specifier: 'catalog:'
version: 0.5.5
'@comfyorg/design-system':
specifier: workspace:*
version: link:packages/design-system
@@ -419,9 +425,6 @@ importers:
extendable-media-recorder-wav-encoder:
specifier: ^7.0.129
version: 7.0.129
fast-glob:
specifier: ^3.3.3
version: 3.3.3
firebase:
specifier: 'catalog:'
version: 11.6.0
@@ -461,6 +464,9 @@ importers:
tiptap-markdown:
specifier: ^0.8.10
version: 0.8.10(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
typegpu:
specifier: 'catalog:'
version: 0.8.2
vue:
specifier: 'catalog:'
version: 3.5.13(typescript@5.9.2)
@@ -558,6 +564,9 @@ importers:
'@vue/test-utils':
specifier: 'catalog:'
version: 2.4.6
'@webgpu/types':
specifier: 'catalog:'
version: 0.1.66
cross-env:
specifier: 'catalog:'
version: 10.1.0
@@ -669,6 +678,9 @@ importers:
unplugin-icons:
specifier: 'catalog:'
version: 0.22.0(@vue/compiler-sfc@3.5.13)
unplugin-typegpu:
specifier: 'catalog:'
version: 0.8.0(typegpu@0.8.2)
unplugin-vue-components:
specifier: 'catalog:'
version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2))
@@ -709,8 +721,8 @@ importers:
apps/desktop-ui:
dependencies:
'@comfyorg/comfyui-electron-types':
specifier: 0.4.73-0
version: 0.4.73-0
specifier: 'catalog:'
version: 0.5.5
'@comfyorg/shared-frontend-utils':
specifier: workspace:*
version: link:../../packages/shared-frontend-utils
@@ -1428,6 +1440,10 @@ packages:
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/standalone@7.28.5':
resolution: {integrity: sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -1453,8 +1469,8 @@ packages:
'@cacheable/utils@2.0.3':
resolution: {integrity: sha512-m7Rce68cMHlAUjvWBy9Ru1Nmw5gU0SjGGtQDdhpe6E0xnbcvrIY0Epy//JU1VYYBUTzrG9jvgmTauULGKzOkWA==}
'@comfyorg/comfyui-electron-types@0.4.73-0':
resolution: {integrity: sha512-WlItGJQx9ZWShNG9wypx3kq+19pSig/U+s5sD2SAeEcMph4u8A/TS+lnRgdKhT58VT1uD7cMcj2SJpfdBPNWvw==}
'@comfyorg/comfyui-electron-types@0.5.5':
resolution: {integrity: sha512-f3XOXpMsALIwHakz7FekVPm4/Fh2pvJPEi8tRe8jYGBt8edsd4Mkkq31Yjs2Weem3BP7yNwbdNuSiQdP/pxJyg==}
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
@@ -3787,8 +3803,8 @@ packages:
peerDependencies:
vue: ^3.5.0
'@webgpu/types@0.1.51':
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
'@webgpu/types@0.1.66':
resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==}
'@xstate/fsm@1.6.5':
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
@@ -4413,6 +4429,9 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -6032,6 +6051,10 @@ packages:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
magic-string-ast@1.0.3:
resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
engines: {node: '>=20.19.0'}
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
@@ -7000,6 +7023,11 @@ packages:
engines: {node: '>= 0.4'}
hasBin: true
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
hasBin: true
restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
@@ -7095,6 +7123,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
hasBin: true
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -7395,6 +7428,14 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyest-for-wgsl@0.1.3:
resolution: {integrity: sha512-Wm5ADG1UyDxykf42S1gLYP4U9e1QP/TdtJeovQi6y68zttpiFLKqQGioHmPs9Mjysh7YMSAr/Lpuk0cD2MVdGA==}
engines: {node: '>=12.20.0'}
tinyest@0.1.2:
resolution: {integrity: sha512-aHRmouyowIq1P5jrTF+YK6pGX+WuvFtSCLbqk91yHnU3SWQRIcNIamZLM5XF6lLqB13AWz0PGPXRff2QGDsxIg==}
engines: {node: '>=12.20.0'}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -7521,6 +7562,13 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
typed-binary@4.3.2:
resolution: {integrity: sha512-HT3pIBM2njCZUmeczDaQUUErGiM6GXFCqMsHegE12HCoBtvHCkfR10JJni0TeGOTnLilTd6YFyj+YhflqQDrDQ==}
typegpu@0.8.2:
resolution: {integrity: sha512-wkMJWhJE0pSkw2G/FesjqjbtHkREyOKu1Zmyj19xfmaX5+65YFwgfQNKSK8CxqN4kJkP7JFelLDJTSYY536TYg==}
engines: {node: '>=12.20.0'}
typescript-eslint@8.44.0:
resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7625,6 +7673,11 @@ packages:
vue-template-es2015-compiler:
optional: true
unplugin-typegpu@0.8.0:
resolution: {integrity: sha512-VJHdXSXGOkAx0WhwFczhVUjAI6HyDkrQXk20HnwyuzIE3FdqE5l9sJTCYZzoVGo3z8i/IA5TMHCDzzP0Bc97Cw==}
peerDependencies:
typegpu: ^0.8.0
unplugin-vue-components@0.28.0:
resolution: {integrity: sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==}
engines: {node: '>=14'}
@@ -7815,8 +7868,8 @@ packages:
vue-component-type-helpers@3.1.1:
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
vue-component-type-helpers@3.1.3:
resolution: {integrity: sha512-V1dOD8XYfstOKCnXbWyEJIrhTBMwSyNjv271L1Jlx9ExpNlCSuqOs3OdWrGJ0V544zXufKbcYabi/o+gK8lyfQ==}
vue-component-type-helpers@3.1.5:
resolution: {integrity: sha512-7V3yJuNWW7/1jxCcI1CswnpDsvs02Qcx/N43LkV+ZqhLj2PKj50slUflHAroNkN4UWiYfzMUUUXiNuv9khmSpQ==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
@@ -8953,6 +9006,8 @@ snapshots:
'@babel/runtime@7.28.4': {}
'@babel/standalone@7.28.5': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -8992,7 +9047,7 @@ snapshots:
'@cacheable/utils@2.0.3': {}
'@comfyorg/comfyui-electron-types@0.4.73-0': {}
'@comfyorg/comfyui-electron-types@0.5.5': {}
'@csstools/color-helpers@5.1.0': {}
@@ -10617,7 +10672,7 @@ snapshots:
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.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.1.3
vue-component-type-helpers: 3.1.5
'@swc/helpers@0.5.17':
dependencies:
@@ -10989,7 +11044,7 @@ snapshots:
'@types/react@19.1.9':
dependencies:
csstype: 3.1.3
csstype: 3.2.3
'@types/semver@7.7.0': {}
@@ -11000,7 +11055,7 @@ snapshots:
'@tweenjs/tween.js': 23.1.3
'@types/stats.js': 0.17.3
'@types/webxr': 0.5.20
'@webgpu/types': 0.1.51
'@webgpu/types': 0.1.66
fflate: 0.8.2
meshoptimizer: 0.18.1
@@ -11503,7 +11558,7 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.9.2)
'@webgpu/types@0.1.51': {}
'@webgpu/types@0.1.66': {}
'@xstate/fsm@1.6.5': {}
@@ -12168,6 +12223,8 @@ snapshots:
csstype@3.1.3: {}
csstype@3.2.3: {}
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
@@ -12594,7 +12651,7 @@ snapshots:
dependencies:
debug: 3.2.7
is-core-module: 2.16.1
resolve: 1.22.10
resolve: 1.22.11
transitivePeerDependencies:
- supports-color
optional: true
@@ -13740,7 +13797,7 @@ snapshots:
acorn: 8.15.0
eslint-visitor-keys: 3.4.3
espree: 9.6.1
semver: 7.7.2
semver: 7.7.3
jsonc-parser@3.2.0: {}
@@ -13982,6 +14039,10 @@ snapshots:
lz-string@1.5.0: {}
magic-string-ast@1.0.3:
dependencies:
magic-string: 0.30.19
magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -15345,6 +15406,13 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
optional: true
restore-cursor@3.1.0:
dependencies:
onetime: 5.1.2
@@ -15449,6 +15517,8 @@ snapshots:
semver@7.7.2: {}
semver@7.7.3: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -15837,6 +15907,12 @@ snapshots:
tinybench@2.9.0: {}
tinyest-for-wgsl@0.1.3:
dependencies:
tinyest: 0.1.2
tinyest@0.1.2: {}
tinyexec@0.3.2: {}
tinyexec@1.0.1: {}
@@ -15968,6 +16044,13 @@ snapshots:
reflect.getprototypeof: 1.0.10
optional: true
typed-binary@4.3.2: {}
typegpu@0.8.2:
dependencies:
tinyest: 0.1.2
typed-binary: 4.3.2
typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
@@ -16063,6 +16146,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
unplugin-typegpu@0.8.0(typegpu@0.8.2):
dependencies:
'@babel/standalone': 7.28.5
defu: 6.1.4
estree-walker: 3.0.3
magic-string-ast: 1.0.3
pathe: 2.0.3
picomatch: 4.0.3
tinyest: 0.1.2
tinyest-for-wgsl: 0.1.3
typegpu: 0.8.2
unplugin: 2.3.5
unplugin-vue-components@0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)):
dependencies:
'@antfu/utils': 0.7.10
@@ -16343,7 +16439,7 @@ snapshots:
vue-component-type-helpers@3.1.1: {}
vue-component-type-helpers@3.1.3: {}
vue-component-type-helpers@3.1.5: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
dependencies:

View File

@@ -4,6 +4,7 @@ packages:
catalog:
'@alloc/quick-lru': ^5.2.0
'@comfyorg/comfyui-electron-types': 0.5.5
'@eslint/js': ^9.35.0
'@iconify-json/lucide': ^1.1.178
'@iconify/json': ^2.2.380
@@ -42,6 +43,7 @@ catalog:
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^11.0.0
'@vueuse/integrations': ^13.9.0
'@webgpu/types': ^0.1.66
algoliasearch: ^5.21.0
axios: ^1.8.2
cross-env: ^10.1.0
@@ -82,9 +84,11 @@ catalog:
tailwindcss-primeui: ^0.6.1
tsx: ^4.15.6
tw-animate-css: ^1.3.8
typegpu: ^0.8.2
typescript: ^5.9.2
typescript-eslint: ^8.44.0
unplugin-icons: ^0.22.0
unplugin-typegpu: 0.8.0
unplugin-vue-components: ^0.28.0
vite: ^5.4.19
vite-plugin-dts: ^4.5.4

View File

@@ -1 +1,9 @@
Thanks to OpenArt (https://openart.ai) for providing the sorted-custom-node-map data, captured in September 2024.
Node usage data merged from two sources:
- Mixpanel "app:node_search_result_selected" events (Nov 2025): 1,112 nodes, 46,514 selections
Reflects actual user search behavior - what users choose when searching.
- OpenArt workflow data (Sept 2024): 2,600 nodes, 118,676 uses
Reflects overall popularity - what's used in workflows.
Merge strategy: New data overwrites old for 514 overlapping nodes. Old data
normalized by 2.55x scale factor to match new data. Total: 3,198 nodes.
Search-selected nodes prioritized in ranking.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env tsx
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
interface ReleaseInfo {
current_version: string
target_minor: number
target_version: string
target_branch: string
needs_release: boolean
latest_patch_tag: string | null
branch_head_sha: string | null
tag_commit_sha: string | null
diff_url: string
release_pr_url: string | null
}
/**
* Execute a command and return stdout
*/
function exec(command: string, cwd?: string): string {
try {
return execSync(command, {
cwd,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe']
}).trim()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
const cwdInfo = cwd ? ` in directory: ${cwd}` : ''
console.error(
`Command failed: ${command}${cwdInfo}\nError: ${errorMessage}`
)
return ''
}
}
/**
* Parse version from requirements.txt
* Handles formats: comfyui-frontend-package==1.2.3, comfyui-frontend-package>=1.2.3, etc.
*/
function parseRequirementsVersion(requirementsPath: string): string | null {
if (!fs.existsSync(requirementsPath)) {
console.error(`Requirements file not found: ${requirementsPath}`)
return null
}
const content = fs.readFileSync(requirementsPath, 'utf-8')
const match = content.match(
/comfyui-frontend-package\s*(?:==|>=|<=|~=|>|<)\s*([0-9]+\.[0-9]+\.[0-9]+)/
)
if (!match) {
console.error(
'Could not find comfyui-frontend-package version in requirements.txt'
)
return null
}
return match[1]
}
/**
* Validate semantic version string
*/
function isValidSemver(version: string): boolean {
if (!version || typeof version !== 'string') {
return false
}
const parts = version.split('.')
if (parts.length !== 3) {
return false
}
return parts.every((part) => {
const num = Number(part)
return Number.isFinite(num) && num >= 0 && String(num) === part
})
}
/**
* Get the latest patch tag for a given minor version
*/
function getLatestPatchTag(repoPath: string, minor: number): string | null {
// Fetch all tags
exec('git fetch --tags', repoPath)
// Use git's native version sorting to get the latest tag
const latestTag = exec(
`git tag -l 'v1.${minor}.*' --sort=-version:refname | head -n 1`,
repoPath
)
if (!latestTag) {
return null
}
// Validate the tag is a valid semver (vX.Y.Z format)
const validTagRegex = /^v\d+\.\d+\.\d+$/
if (!validTagRegex.test(latestTag)) {
console.error(
`Latest tag for minor version ${minor} is not valid semver: ${latestTag}`
)
return null
}
return latestTag
}
/**
* Resolve the ComfyUI release information
*/
function resolveRelease(
comfyuiRepoPath: string,
frontendRepoPath: string
): ReleaseInfo | null {
// Parse current version from ComfyUI requirements.txt
const requirementsPath = path.join(comfyuiRepoPath, 'requirements.txt')
const currentVersion = parseRequirementsVersion(requirementsPath)
if (!currentVersion) {
return null
}
// Validate version format
if (!isValidSemver(currentVersion)) {
console.error(
`Invalid semantic version format: ${currentVersion}. Expected format: X.Y.Z`
)
return null
}
const [major, currentMinor, patch] = currentVersion.split('.').map(Number)
// Calculate target minor version (next minor)
const targetMinor = currentMinor + 1
const targetBranch = `core/1.${targetMinor}`
// Check if target branch exists in frontend repo
exec('git fetch origin', frontendRepoPath)
const branchExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!branchExists) {
console.error(
`Target branch ${targetBranch} does not exist in frontend repo`
)
return null
}
// Get latest patch tag for target minor
const latestPatchTag = getLatestPatchTag(frontendRepoPath, targetMinor)
let needsRelease = false
let branchHeadSha: string | null = null
let tagCommitSha: string | null = null
let targetVersion = currentVersion
if (latestPatchTag) {
// Get commit SHA for the tag
tagCommitSha = exec(`git rev-list -n 1 ${latestPatchTag}`, frontendRepoPath)
// Get commit SHA for branch head
branchHeadSha = exec(
`git rev-parse origin/${targetBranch}`,
frontendRepoPath
)
// Check if there are commits between tag and branch head
const commitsBetween = exec(
`git rev-list ${latestPatchTag}..origin/${targetBranch} --count`,
frontendRepoPath
)
const commitCount = parseInt(commitsBetween, 10)
needsRelease = !isNaN(commitCount) && commitCount > 0
// Parse existing patch number and increment if needed
const tagVersion = latestPatchTag.replace('v', '')
// Validate tag version format
if (!isValidSemver(tagVersion)) {
console.error(
`Invalid tag version format: ${tagVersion}. Expected format: X.Y.Z`
)
return null
}
const [, , existingPatch] = tagVersion.split('.').map(Number)
// Validate existingPatch is a valid number
if (!Number.isFinite(existingPatch) || existingPatch < 0) {
console.error(`Invalid patch number in tag: ${existingPatch}`)
return null
}
if (needsRelease) {
targetVersion = `1.${targetMinor}.${existingPatch + 1}`
} else {
targetVersion = tagVersion
}
} else {
// No tags exist for this minor version, need to create v1.{targetMinor}.0
needsRelease = true
targetVersion = `1.${targetMinor}.0`
branchHeadSha = exec(
`git rev-parse origin/${targetBranch}`,
frontendRepoPath
)
}
const diffUrl = `https://github.com/Comfy-Org/ComfyUI_frontend/compare/v${currentVersion}...v${targetVersion}`
return {
current_version: currentVersion,
target_minor: targetMinor,
target_version: targetVersion,
target_branch: targetBranch,
needs_release: needsRelease,
latest_patch_tag: latestPatchTag,
branch_head_sha: branchHeadSha,
tag_commit_sha: tagCommitSha,
diff_url: diffUrl,
release_pr_url: null // Will be populated by workflow if release is triggered
}
}
// Main execution
const comfyuiRepoPath = process.argv[2]
const frontendRepoPath = process.argv[3] || process.cwd()
if (!comfyuiRepoPath) {
console.error(
'Usage: resolve-comfyui-release.ts <comfyui-repo-path> [frontend-repo-path]'
)
process.exit(1)
}
const releaseInfo = resolveRelease(comfyuiRepoPath, frontendRepoPath)
if (!releaseInfo) {
console.error('Failed to resolve release information')
process.exit(1)
}
// Output as JSON for GitHub Actions
// eslint-disable-next-line no-console
console.log(JSON.stringify(releaseInfo, null, 2))
export { resolveRelease }

View File

@@ -1,41 +1,92 @@
<template>
<div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
<div
v-if="!workspaceStore.focusMode"
class="ml-1 flex gap-x-0.5 pt-1"
@mouseenter="isTopMenuHovered = true"
@mouseleave="isTopMenuHovered = false"
>
<div class="min-w-0 flex-1">
<SubgraphBreadcrumb />
</div>
<div
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
>
<ActionBarButtons />
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div class="mx-1 flex flex-col items-end gap-1">
<div
ref="legacyCommandsContainerRef"
class="[&:not(:has(*>*:not(:empty)))]:hidden"
></div>
<ComfyActionbar />
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
<LoginButton v-else-if="isDesktop" />
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-interface-stroke px-2 shadow-interface"
>
<ActionBarButtons />
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
ref="legacyCommandsContainerRef"
class="[&:not(:has(*>*:not(:empty)))]:hidden"
></div>
<ComfyActionbar />
<IconButton
v-tooltip.bottom="queueHistoryTooltipConfig"
type="transparent"
size="sm"
class="queue-history-toggle relative mr-2 transition-colors duration-200 ease-in-out hover:bg-secondary-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background"
:class="queueHistoryButtonBackgroundClass"
:aria-pressed="isQueueOverlayExpanded"
:aria-label="
t('sideToolbar.queueProgressOverlay.expandCollapsedQueue')
"
@click="toggleQueueOverlay"
>
<i
class="icon-[lucide--history] block size-4 text-muted-foreground"
/>
<span
v-if="queuedCount > 0"
class="absolute -top-1 -right-1 min-w-[16px] rounded-full bg-primary-background py-0.25 text-[10px] font-medium leading-[14px] text-white"
>
{{ queuedCount }}
</span>
</IconButton>
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
<LoginButton v-else-if="isDesktop" />
</div>
<QueueProgressOverlay
v-model:expanded="isQueueOverlayExpanded"
:menu-hovered="isTopMenuHovered"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import IconButton from '@/components/button/IconButton.vue'
import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
import { app } from '@/scripts/app'
import { useQueueStore } from '@/stores/queueStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isElectron } from '@/utils/envUtil'
const workspaceStore = useWorkspaceStore()
const { isLoggedIn } = useCurrentUser()
const isDesktop = isElectron()
const { t } = useI18n()
const isQueueOverlayExpanded = ref(false)
const queueStore = useQueueStore()
const isTopMenuHovered = ref(false)
const queuedCount = computed(() => queueStore.pendingTasks.length)
const queueHistoryTooltipConfig = computed(() =>
buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))
)
const queueHistoryButtonBackgroundClass = computed(() =>
isQueueOverlayExpanded.value
? 'bg-secondary-background-selected'
: 'bg-secondary-background'
)
// Maintain support for legacy topbar elements attached by custom scripts
const legacyCommandsContainerRef = ref<HTMLElement>()
@@ -45,6 +96,10 @@ onMounted(() => {
legacyCommandsContainerRef.value.appendChild(app.menu.element)
}
})
const toggleQueueOverlay = () => {
isQueueOverlayExpanded.value = !isQueueOverlayExpanded.value
}
</script>
<style scoped>

View File

@@ -10,7 +10,7 @@
</div>
<Panel
class="pointer-events-auto z-1000"
class="pointer-events-auto"
:style="style"
:class="panelClass"
:pt="{
@@ -66,12 +66,7 @@ const storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', {
x: 0,
y: 0
})
const {
x,
y,
style: style,
isDragging
} = useDraggable(panelRef, {
const { x, y, style, isDragging } = useDraggable(panelRef, {
initialValue: { x: 0, y: 0 },
handle: dragHandleRef,
containerElement: document.body,
@@ -257,16 +252,17 @@ watch(isDragging, (dragging) => {
})
const actionbarClass = computed(() =>
cn(
'w-[265px] border-dashed border-blue-500 opacity-80',
'w-[200px] border-dashed border-blue-500 opacity-80',
'm-1.5 flex items-center justify-center self-stretch',
'rounded-md before:w-50 before:-ml-50 before:h-full',
'pointer-events-auto',
isMouseOverDropZone.value &&
'border-[3px] opacity-100 scale-105 shadow-[0_0_20px] shadow-blue-500'
)
)
const panelClass = computed(() =>
cn(
'actionbar pointer-events-auto z1000',
'actionbar pointer-events-auto z-1300',
isDragging.value && 'select-none pointer-events-none',
isDocked.value
? 'p-0 static mr-2 border-none bg-transparent'

View File

@@ -2,9 +2,7 @@
<div class="queue-button-group flex">
<SplitButton
v-tooltip.bottom="{
value: workspaceStore.shiftDown
? $t('menu.runWorkflowFront')
: $t('menu.runWorkflow'),
value: queueButtonTooltip,
showDelay: 600
}"
class="comfyui-queue-button"
@@ -16,16 +14,7 @@
@click="queuePrompt"
>
<template #icon>
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
<i
v-else-if="queueMode === 'instant'"
class="icon-[lucide--fast-forward]"
/>
<i
v-else-if="queueMode === 'change'"
class="icon-[lucide--step-forward]"
/>
<i :class="iconClass" />
</template>
<template #item="{ item }">
<Button
@@ -42,46 +31,12 @@
</template>
</SplitButton>
<BatchCountEdit />
<ButtonGroup class="execution-actions flex flex-nowrap">
<Button
v-tooltip.bottom="{
value: $t('menu.interrupt'),
showDelay: 600
}"
icon="pi pi-times"
:severity="executingPrompt ? 'danger' : 'secondary'"
:disabled="!executingPrompt"
text
:aria-label="$t('menu.interrupt')"
@click="() => commandStore.execute('Comfy.Interrupt')"
/>
<Button
v-tooltip.bottom="{
value: $t('sideToolbar.queueTab.clearPendingTasks'),
showDelay: 600
}"
icon="pi pi-stop"
:severity="hasPendingTasks ? 'danger' : 'secondary'"
:disabled="!hasPendingTasks"
text
:aria-label="$t('sideToolbar.queueTab.clearPendingTasks')"
@click="
() => {
if (queueCountStore.count.value > 1) {
commandStore.execute('Comfy.ClearPendingTasks')
}
queueMode = 'disabled'
}
"
/>
</ButtonGroup>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import ButtonGroup from 'primevue/buttongroup'
import type { MenuItem } from 'primevue/menuitem'
import SplitButton from 'primevue/splitbutton'
import { computed } from 'vue'
@@ -89,19 +44,23 @@ import { useI18n } from 'vue-i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import {
useQueuePendingTaskCountStore,
useQueueSettingsStore
} from '@/stores/queueStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useQueueSettingsStore } from '@/stores/queueStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
import BatchCountEdit from '../BatchCountEdit.vue'
const workspaceStore = useWorkspaceStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()
const queueModeMenuItemLookup = computed(() => {
const items: Record<string, MenuItem> = {
@@ -152,10 +111,34 @@ const queueModeMenuItems = computed(() =>
Object.values(queueModeMenuItemLookup.value)
)
const executingPrompt = computed(() => !!queueCountStore.count.value)
const hasPendingTasks = computed(
() => queueCountStore.count.value > 1 || queueMode.value !== 'disabled'
)
const iconClass = computed(() => {
if (hasMissingNodes.value) {
return 'icon-[lucide--triangle-alert]'
}
if (workspaceStore.shiftDown) {
return 'icon-[lucide--list-start]'
}
if (queueMode.value === 'disabled') {
return 'icon-[lucide--play]'
}
if (queueMode.value === 'instant') {
return 'icon-[lucide--fast-forward]'
}
if (queueMode.value === 'change') {
return 'icon-[lucide--step-forward]'
}
return 'icon-[lucide--play]'
})
const queueButtonTooltip = computed(() => {
if (hasMissingNodes.value) {
return t('menu.runWorkflowDisabled')
}
if (workspaceStore.shiftDown) {
return t('menu.runWorkflowFront')
}
return t('menu.runWorkflow')
})
const commandStore = useCommandStore()
const queuePrompt = async (e: Event) => {

View File

@@ -2,7 +2,7 @@
<a
ref="wrapperRef"
v-tooltip.bottom="{
value: item.label,
value: tooltipText,
showDelay: 512
}"
draggable="false"
@@ -16,6 +16,10 @@
}"
@click="handleClick"
>
<i
v-if="hasMissingNodes && isRoot"
class="icon-[lucide--triangle-alert] text-warning-background"
/>
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
<Tag v-if="item.isBlueprint" :value="'Blueprint'" severity="primary" />
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
@@ -60,10 +64,13 @@ import {
ComfyWorkflow,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useCommandStore } from '@/stores/commandStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { appendJsonExt } from '@/utils/formatUtil'
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
interface Props {
item: MenuItem
@@ -74,6 +81,11 @@ const props = withDefaults(defineProps<Props>(), {
isActive: false
})
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()
const menu = ref<InstanceType<typeof Menu> & MenuState>()
const dialogService = useDialogService()
@@ -115,6 +127,14 @@ const rename = async (
}
const isRoot = props.item.key === 'root'
const tooltipText = computed(() => {
if (hasMissingNodes.value && isRoot) {
return t('breadcrumbsMenu.missingNodesWarning')
}
return props.item.label
})
const menuItems = computed<MenuItem[]>(() => {
return [
{

View File

@@ -24,7 +24,7 @@ import {
import { cn } from '@/utils/tailwindUtil'
interface IconButtonProps extends BaseButtonProps {
onClick: (event: Event) => void
onClick?: (event: MouseEvent) => void
}
defineOptions({

View File

@@ -1,17 +1,15 @@
<template>
<div :class="iconGroupClasses">
<div
:class="
cn(
'flex justify-center items-center shrink-0 outline-hidden border-none p-0 rounded-lg bg-secondary-background shadow-sm transition-all duration-200 cursor-pointer'
)
"
>
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { cn } from '@/utils/tailwindUtil'
const iconGroupClasses = cn(
'flex justify-center items-center shrink-0',
'outline-hidden border-none p-0 rounded-lg',
'bg-secondary-background shadow-sm',
'transition-all duration-200',
'cursor-pointer'
)
</script>

View File

@@ -47,7 +47,7 @@ const {
} = defineProps<IconTextButtonProps>()
const buttonStyle = computed(() => {
const baseClasses = `${getBaseButtonClasses()} justify-start! gap-2`
const baseClasses = `${getBaseButtonClasses()} justify-start gap-2`
const sizeClasses = getButtonSizeClasses(size)
const typeClasses = border
? getBorderButtonTypeClasses(type)

View File

@@ -1,6 +1,6 @@
<template>
<div class="relative inline-flex items-center">
<IconButton :size="size" :type="type" @click="toggle">
<IconButton :size="size" :type="type" @click="popover?.toggle">
<i v-if="!isVertical" class="icon-[lucide--ellipsis] text-sm" />
<i v-else class="icon-[lucide--more-vertical] text-sm" />
</IconButton>
@@ -25,8 +25,18 @@
)
}
}"
@show="$emit('menuOpened')"
@hide="$emit('menuClosed')"
@show="
() => {
isOpen = true
$emit('menuOpened')
}
"
@hide="
() => {
isOpen = false
$emit('menuClosed')
}
"
>
<div class="flex min-w-40 flex-col gap-2 p-2">
<slot :close="hide" />
@@ -48,8 +58,6 @@ interface MoreButtonProps extends BaseButtonProps {
isVertical?: boolean
}
const popover = ref<InstanceType<typeof Popover>>()
const {
size = 'md',
type = 'secondary',
@@ -61,11 +69,15 @@ defineEmits<{
menuClosed: []
}>()
const toggle = (event: Event) => {
popover.value?.toggle(event)
}
const isOpen = ref(false)
const popover = ref<InstanceType<typeof Popover>>()
const hide = () => {
function hide() {
popover.value?.hide()
}
defineExpose({
hide,
isOpen
})
</script>

View File

@@ -11,7 +11,7 @@ import { cn } from '@/utils/tailwindUtil'
const { label, variant = 'dark' } = defineProps<{
label: string
variant?: 'dark' | 'light'
variant?: 'dark' | 'light' | 'gray'
}>()
const baseClasses =
@@ -19,7 +19,10 @@ const baseClasses =
const variantStyles = {
dark: 'bg-zinc-500/40 text-white/90',
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground')
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground'),
gray: cn(
'backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground'
)
}
const chipClasses = computed(() => {

View File

@@ -18,8 +18,8 @@
...inputAttrs
}
}"
@keyup.enter="blurInputElement"
@keyup.escape="cancelEditing"
@keyup.enter.capture.stop="blurInputElement"
@keyup.escape.stop="cancelEditing"
@click.stop
@pointerdown.stop.capture
@pointermove.stop.capture
@@ -38,7 +38,7 @@ const {
} = defineProps<{
modelValue: string
isEditing?: boolean
inputAttrs?: Record<string, any>
inputAttrs?: Record<string, string>
}>()
const emit = defineEmits(['update:modelValue', 'edit', 'cancel'])

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