Commit Graph

706 Commits

Author SHA1 Message Date
Comfy Org PR Bot
f4828c4a25 [backport cloud/1.41] fix: remove timeouts from error toasts so they persist until dismissed (#9553)
Backport of #9543 to `cloud/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9553-backport-cloud-1-41-fix-remove-timeouts-from-error-toasts-so-they-persist-until-dismis-31d6d73d365081d39183dcd7c83b3aa0)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2026-03-07 18:06:01 -08:00
Comfy Org PR Bot
1c9edeb604 [backport cloud/1.41] fix: Prevent corruption of workflow data due to checkState during graph loading (#9537)
Backport of #9531 to `cloud/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9537-backport-cloud-1-41-fix-Prevent-corruption-of-workflow-data-due-to-checkState-during-g-31c6d73d3650810a9963e85b18084e31)
by [Unito](https://www.unito.io)

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-03-07 14:01:55 -08:00
pythongosssss
4ff14b5eb9 feat/fix: App mode QA updates (#9439)
## Summary

Various fixes from app mode QA

## Changes

- **What**: 
- fix: prevent inserting nodes from workflow/apps sidebar tabs
- fix: hide json extension in workflow tab
- fix: hide apps nav button in apps tab when already in apps mode
- fix: center text on arrange page
- fix: prevent IoItems from "jumping" due to stale transform after drag
and drop op
- fix: refactor side panels and add custom stable pixel based sizing
- fix: make outputs/inputs lists in app builder scrollable
- fix: fix rerun not working correctly

- feat: add text to interrupt button
- feat: add enter app mode button to builder toolbar
- feat: add tooltip to download button on linear view
- feat: show last output of workflow in arrange tab if available
- feat: show download count in download all button, hide if only 1 asset
to download

## Review Focus

- Rerun - I am not sure why it was triggering widget actions, removing
it seemed like the correct fix
- useStablePrimeVueSplitter - this is a workaround for the fact it uses
percent sizing, I also tried switching to reka-ui splitters, but they
also only support % sizing in our version [pixel based looks to have
been added in a newer version, will log an issue to upgrade & replace
splitters with this]


## Screenshots (if applicable)

<img width="1314" height="1129" alt="image"
src="https://github.com/user-attachments/assets/c430f9d6-7c29-4853-803e-5b6fe7086fca"
/>
<img width="511" height="283" alt="image"
src="https://github.com/user-attachments/assets/b7e594d4-70a1-41e3-8ba1-78512f2a5c8b"
/>
<img width="254" height="232" alt="image"
src="https://github.com/user-attachments/assets/1d146399-39ea-4b0e-928c-340b74957535"
/>
<img width="487" height="198" alt="image"
src="https://github.com/user-attachments/assets/e2ba7f5d-8ff5-47f4-9526-61ebb99514b8"
/>
<img width="378" height="647" alt="image"
src="https://github.com/user-attachments/assets/a47a3054-9320-4327-bdc0-b0a16e19f83d"
/>
<img width="1016" height="476" alt="image"
src="https://github.com/user-attachments/assets/479ae50e-d380-4d56-a5c9-5df142b14ed0"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9439-feat-fix-App-mode-QA-updates-31a6d73d365081b38337d63207b88817)
by [Unito](https://www.unito.io)
2026-03-06 20:02:19 +00:00
Johnpaul Chiwetelu
5e17bbbf85 feat: expose litegraph internal keybindings (#9459)
## Summary

Migrate hardcoded litegraph canvas keybindings (Ctrl+A/C/V, Delete,
Backspace) into the customizable keybinding system so users can remap
them via Settings > Keybindings.

## Changes

- **What**: Register Ctrl+A (SelectAll), Ctrl+C (CopySelected), Ctrl+V
(PasteFromClipboard), Ctrl+Shift+V (PasteFromClipboardWithConnect),
Delete/Backspace (DeleteSelectedItems) as core keybindings in
`defaults.ts`. Add new `PasteFromClipboardWithConnect` command. Remove
hardcoded handling from litegraph `processKey()`, the `app.ts` Ctrl+C/V
monkey-patch, and the `keybindingService` canvas forwarding logic.

Fixes #1082
Fixes #2015

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9459-feat-expose-litegraph-internal-keybindings-31b6d73d3650819a8499fd96c8a6678f)
by [Unito](https://www.unito.io)
2026-03-06 18:30:35 +01:00
Christian Byrne
a0abe3e36f chore: add deprecation warning for legacy queue/history menu (#9460)
## Summary

Add console.warn deprecation notice when the legacy ComfyList
queue/history menu is instantiated.

## Changes

- **What**: Log a deprecation warning in the `ComfyList` constructor
telling users the legacy menu is deprecated, may break, and won't
receive support. Includes instructions to switch via Settings → "Use new
menu" → "Top".

## Review Focus

Wording of the user-facing console warning.

Fixes #8100 (Phase 1)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9460-chore-add-deprecation-warning-for-legacy-queue-history-menu-31b6d73d365081ffa041cad33e8cd9a7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-06 00:32:47 -08:00
Alexander Brown
1bac5d9bdd feat: workflow sharing and ComfyHub publish flow (#8951)
## Summary

Add workflow sharing by URL and a multi-step ComfyHub publish wizard,
gated by feature flags and an optional profile gate.

## Changes

- **What**: Share dialog with URL generation and asset warnings;
ComfyHub publish wizard (Describe → Examples → Finish) with thumbnail
upload and tags; profile gate flow; shared workflow URL loader with
confirmation dialog
- **Dependencies**: None (new `sharing/` module under
`src/platform/workflow/`)

## Review Focus

- Three new feature flags: `workflow_sharing_enabled`,
`comfyhub_upload_enabled`, `comfyhub_profile_gate_enabled`
- Share service API contract and stale-share detection
(`workflowShareService.ts`)
- Publish wizard and profile gate state management
- Shared workflow URL loading and query-param preservation

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8951-feat-share-workflow-by-URL-30b6d73d3650813ebbfafdad775bfb33)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-05 16:33:06 -08:00
Christian Byrne
e7588c33e1 refactor: rename imagePreviewStore to nodeOutputStore (#9416)
## Summary

Rename `imagePreviewStore.ts` → `nodeOutputStore.ts` to match the store
it houses (`useNodeOutputStore`, Pinia ID `nodeOutput`).

## Changes

- **What**: Rename file + test file, update all 21 import paths, mock
paths, and describe labels
- **Breaking**: None — exported symbol (`useNodeOutputStore`) and Pinia
store ID (`nodeOutput`) are unchanged

## Custom Node Ecosystem Audit

Searched the ComfyUI custom node ecosystem for `imagePreviewStore` and
`useNodeOutputStore`:
- **Not part of the public API** — neither filename nor export appear in
`comfyui_frontend_package` or `vite.types.config.mts`
- **1 external repo found:** `wallen0322/ComfyUI-AE-Animation` —
contains a full fork of the frontend source tree; it copies the file
internally and does not import from the published package. **No
breakage.**
- **No custom nodes import this store via the extension API.** This is a
safe internal-only rename.

## Review Focus

Pure mechanical rename — no logic changes. Verify no stale
`imagePreviewStore` references remain.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9416-refactor-rename-imagePreviewStore-to-nodeOutputStore-31a6d73d3650816086c5e62959861ddb)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-05 13:52:50 -08:00
pythongosssss
5376b7ed1e feat: App mode empty graph handling (#9393)
## Summary

Adds handling for entering app mode with an empty graph prompting the
user to load a template as a starting point

## Changes

- **What**: 
- app mode handle empty workflows, disable builder button, show
different message
- fix fitView when switching from app mode to graph

## Review Focus

Moving the fitView since the canvas is hidden in app mode until after
the workflow is loaded and the mode has been switched back to graph, I
don't see how this could cause any issues but worth a closer eye

## Screenshots (if applicable)

<img width="1057" height="916" alt="image"
src="https://github.com/user-attachments/assets/2ffe2b6d-9ce1-4218-828a-b7bc336c365a"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9393-feat-App-mode-empty-graph-handling-3196d73d3650812cab0ce878109ed5c9)
by [Unito](https://www.unito.io)
2026-03-05 02:27:05 -08:00
Christian Byrne
6dc4a3ed1e refactor(changeTracker): use shared clone util and centralize nodeOutputs snapshot (#9387)
## Summary

Follow-up to #9380. Replaces local `clone()` with shared util and
centralizes output snapshotting.

## Changes

- **What**: Replaced local `JSON.parse(JSON.stringify)` clone in
`changeTracker.ts` with shared `clone()` from `@/scripts/utils` (prefers
`structuredClone` with JSON fallback). Added `snapshotOutputs()` to
`useNodeOutputStore` as symmetric counterpart to existing
`restoreOutputs()`, and wired `changeTracker.store()` to use it.
- **Breaking**: None

## Review Focus

Symmetry between `snapshotOutputs()` and `restoreOutputs()` in the node
output store.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9387-refactor-changeTracker-use-shared-clone-util-and-centralize-nodeOutputs-snapshot-3196d73d365081a289c3cb414f57929e)
by [Unito](https://www.unito.io)
2026-03-04 20:03:26 -08:00
Kelly Yang
1e86e8c4d5 [Bug] Node preview images are lost when switching between multiple workflow tabs (#9380)
## Summary

When working with multiple workflow tabs, the internal preview (image
thumbnail) of nodes like Load Image disappears after navigating away
from and back to a tab. This affects all active tabs once the switch
occurs.

## Screenshot
before


https://github.com/user-attachments/assets/99466123-37db-406f-9e17-0a9ff22311c3



after




https://github.com/user-attachments/assets/bdad0ef1-72b7-46c7-aa61-0a557688e55e

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-04 20:58:57 +00:00
pythongosssss
0d7dc15916 App mode output feed to only show current session results for outputs defined in the app (#9307)
## Summary

Updates app mode to only show images from:
- the workflow that generated the image
- in the current session
- for the outputs selected in the builder

## Changes

- **What**: 
- adds new mapping of jobid -> workflow path [cant use id here as it is
not guaranteed unique], capped at 4k entries
- fix bug where executing a workflow then quickly switching tabs
associated incorrect workflow
- add missing output history tests

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9307-App-mode-output-feed-to-only-show-current-session-results-for-outputs-defined-in-the-app-3156d73d36508142b4bbca3f938fc5c2)
by [Unito](https://www.unito.io)
2026-03-02 11:10:20 -08:00
jaeone94
a0e518aa98 refactor(node-replacement): reorganize domain components and expand comprehensive test suite (#9301)
## Summary

Resolves six open issues by reorganizing node replacement components
into a domain-driven folder structure, refactoring event handling to
follow the emit pattern, and adding comprehensive test coverage across
all affected modules.

## Changes

- **What**:
- Moved `SwapNodeGroupRow.vue` and `SwapNodesCard.vue` from
`src/components/rightSidePanel/errors/` to
`src/platform/nodeReplacement/components/` (Issues #9255)
- Moved `useMissingNodeScan.ts` from `src/composables/` to
`src/platform/nodeReplacement/missingNodeScan.ts`, renamed to reflect it
is a plain function not a Vue composable (Issues #9254)
- Refactored `SwapNodeGroupRow.vue` to emit a `'replace'` event instead
of calling `useNodeReplacement()` and `useExecutionErrorStore()`
directly; replacement logic now handled in `TabErrors.vue` (Issue #9267)
- Added unit tests for `removeMissingNodesByType`
(`executionErrorStore.test.ts`), `scanMissingNodes`
(`missingNodeScan.test.ts`), and `swapNodeGroups` computed
(`swapNodeGroups.test.ts`, `useErrorGroups.test.ts`) (Issue #9270)
- Added placeholder detection tests covering unregistered-type detection
when `has_errors` is false, and exclusion of registered types
(`useNodeReplacement.test.ts`) (Issue #9271)
- Added component tests for `MissingNodeCard` and `MissingPackGroupRow`
covering rendering, expand/collapse, events, install states, and edge
cases (Issue #9231)
- Added component tests for `SwapNodeGroupRow` and `SwapNodesCard`
(Issues #9255, #9267)

## Additional Changes (Post-Review)

- **Edge case guard in placeholder detection**
(`useNodeReplacement.ts`): When `last_serialization.type` is absent (old
serialization format), the predicate falls back to `n.type`, which the
app may have already run through `sanitizeNodeName` — stripping HTML
special characters (`& < > " ' \` =`). In that case, a `Set.has()`
lookup against the original unsanitized type name would silently miss,
causing replacement to be skipped.

Fixed by including sanitized variants of each target type in the
`targetTypes` Set at construction time. For the overwhelmingly common
case (no special characters in type names), the Set deduplicates the
entries and runtime behavior is identical to before.

A regression test was added to cover the specific scenario:
`last_serialization.type` absent + live `n.type` already sanitized.

## Review Focus

- `TabErrors.vue`: confirm the new `@replace` event handler correctly
replaces nodes and removes them from missing nodes list (mirrors the old
inline logic in `SwapNodeGroupRow`)
- `missingNodeScan.ts`: filename/export name change from
`useMissingNodeScan` — verify all call sites updated via `app.ts`
- Test mocking strategy: module-level `vi.mock()` factories use closures
over `ref`/plain objects to allow per-test overrides without global
mutable state

- Fixes #9231
- Fixes #9254
- Fixes #9255
- Fixes #9267
- Fixes #9270
- Fixes #9271
2026-02-28 06:17:30 -08:00
pythongosssss
f83daa6f3b App mode - discard slow preview messages to prevent overwriting output image (#9261)
## Summary

Prevent latent previews received after the job/node has already finished
processing overwriting the actual output display

## Changes

- **What**: 
- updates job preview store to also track which node the preview was for
- updates linear progress tracking to store executed nodes enabling
skipping previews of these

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

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

## Screenshots (if applicable)

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9261-App-mode-discard-slow-preview-messages-to-prevent-overwriting-output-image-3136d73d3650817884c2ce2ff5993b9e)
by [Unito](https://www.unito.io)
2026-02-27 10:58:41 -08:00
jaeone94
1c3984a178 feat: add node replacement UI to Errors Tab (#9253)
## Summary

Adds a node replacement UI to the Errors Tab so users can swap missing
nodes with compatible alternatives directly from the error panel,
without opening a separate dialog.

## Changes

- **What**: New `SwapNodesCard` and `SwapNodeGroupRow` components render
swap groups in the Errors Tab; each group shows the missing node type,
its instances (with locate buttons), and a Replace button. Added
`useMissingNodeScan` composable to scan the graph for missing nodes and
populate `executionErrorStore`. Added `removeMissingNodesByType()` to
`executionErrorStore` so replaced nodes are pruned from the error list
reactively.

## Bug Fixes Found During Implementation

### Bug 1: Replaced nodes render as empty shells until page refresh

`replaceWithMapping()` directly mutates `_nodes[idx]`, bypassing the Vue
rendering pipeline entirely. Because the replacement node reuses the
same ID, `vueNodeData` retains the stale entry from the old placeholder
(`hasErrors: true`, empty widgets/inputs). `graph.setDirtyCanvas()` only
repaints the LiteGraph canvas and has no effect on Vue.

**Fix**: After `replaceWithMapping()`, manually call
`nodeGraph.onNodeAdded?.(newNode)` to trigger `handleNodeAdded` in
`useGraphNodeManager`, which runs `extractVueNodeData(newNode)` and
updates `vueNodeData` correctly. Also added a guard in `handleNodeAdded`
to skip `layoutStore.createNode()` when a layout for the same ID already
exists, preventing a duplicate `spatialIndex.insert()`.

### Bug 2: Missing node error list overwritten by incomplete server
response

Two compounding issues: (A) the server's `missing_node_type` error only
reports the *first* missing node — the old handler parsed this and
called `surfaceMissingNodes([singleNode])`, overwriting the full list
collected at load time. (B) `queuePrompt()` calls `clearAllErrors()`
before the API request; if the subsequent rescan used the stale
`has_errors` flag and found nothing, the missing nodes were permanently
lost.

**Fix**: Created `useMissingNodeScan.ts` which scans
`LiteGraph.registered_node_types` directly (not `has_errors`). The
`missing_node_type` catch block in `app.ts` now calls
`rescanAndSurfaceMissingNodes(this.rootGraph)` instead of parsing the
server's partial response.

## Review Focus

- `handleReplaceNode` removes the group from the store only when
`replaceNodesInPlace` returns at least one replaced node — should we
always clear, or only on full success?
- `useMissingNodeScan` re-scans on every execution-error change; confirm
no performance concerns for large graphs with many subgraphs.


## Screenshots 


https://github.com/user-attachments/assets/78310fc4-0424-4920-b369-cef60a123d50



https://github.com/user-attachments/assets/3d2fd5e1-5e85-4c20-86aa-8bf920e86987



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9253-feat-add-node-replacement-UI-to-Errors-Tab-3136d73d365081718d4ddfd628cb4449)
by [Unito](https://www.unito.io)
2026-02-26 17:37:48 -08:00
Christian Byrne
6a08e4ddde Revert "fix: sync DOM widget values to widgetValueStore on registration" (#9205)
Reverts Comfy-Org/ComfyUI_frontend#9166

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9205-Revert-fix-sync-DOM-widget-values-to-widgetValueStore-on-registration-3126d73d365081df8944d3c6508d2372)
by [Unito](https://www.unito.io)
2026-02-25 21:36:52 -08:00
Terry Jia
5cfd1aa77e feat: add Painter Node (#8521)
## Summary
Add PainterNode widget for freehand mask drawing directly on the canvas,
with brush/eraser tools, opacity, hardness, and background color
controls.

need BE changes https://github.com/Comfy-Org/ComfyUI/pull/12294

## Screenshots (if applicable)


https://github.com/user-attachments/assets/7222063a-0e40-40bb-b72e-b42c8984beb9



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8521-feat-add-Painter-Node-2fa6d73d36508124ab2ede449a0cc67a)
by [Unito](https://www.unito.io)
2026-02-25 21:08:49 -08:00
jaeone94
80fe51bb8c feat: show missing node packs in Errors Tab with install support (#9213)
## Summary

Surfaces missing node pack information in the Errors Tab, grouped by
registry pack, with one-click install support via ComfyUI Manager.

## Changes

- **What**: Errors Tab now groups missing nodes by their registry pack
and shows a `MissingPackGroupRow` with pack name, node/pack counts, and
an Install button that triggers Manager installation. A
`MissingNodeCard` shows individual unresolvable nodes that have no
associated pack. `useErrorGroups` was extended to resolve missing node
types to their registry packs using the `/api/workflow/missing_nodes`
endpoint. `executionErrorStore` was refactored to track missing node
types separately from execution errors and expose them reactively.
- **Breaking**: None

## Review Focus

- `useErrorGroups.ts` — the new `resolveMissingNodePacks` logic fetches
pack metadata and maps node types to pack IDs; edge cases around partial
resolution (some nodes have a pack, some don't) produce both
`MissingPackGroupRow` and `MissingNodeCard` entries
- `executionErrorStore.ts` — the store now separates `missingNodeTypes`
state from `errors`; the deferred-warnings path in `app.ts` now calls
`setMissingNodeTypes` so the Errors Tab is populated even when a
workflow loads without executing

## Screenshots (if applicable)


https://github.com/user-attachments/assets/97f8d009-0cac-4739-8740-fd3333b5a85b


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9213-feat-show-missing-node-packs-in-Errors-Tab-with-install-support-3126d73d36508197bc4bf8ebfd2125c8)
by [Unito](https://www.unito.io)
2026-02-25 20:25:47 -08:00
Hunter
8c3738fb77 feat: add Free subscription tier support (#8864)
## Summary

Add frontend support for a Free subscription tier — login/signup page
restructuring, telemetry instrumentation, and tier-aware billing gating.

## Changes

- **What**: 
- Restructure login/signup pages: OAuth buttons promoted as primary
sign-in method, email login available via progressive disclosure
- Add Free tier badge on Google sign-up button with dynamic credit count
from remote config
- Add `FREE` subscription tier to type system (tier pricing, tier rank,
registry types)
  - Add `isFreeTier` computed to `useSubscription()`
- Disable credit top-up for Free tier users (dialogService,
purchaseCredits, popover CTA)
- Show subscription/upgrade dialog instead of top-up dialog when Free
tier user hits out-of-credits
- Add funnel telemetry: `trackLoginOpened`, enrich `trackSignupOpened`
with `free_tier_badge_shown`, track email toggle clicks

## Review Focus

- Tier gating logic: Free tier users should see "Upgrade" instead of
"Add Credits" and never reach the top-up flow
- Telemetry event design for Mixpanel funnel analysis
- Progressive disclosure UX on login/signup pages

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8864-feat-add-Free-subscription-tier-support-3076d73d36508133b84ec5f0a67ccb03)
by [Unito](https://www.unito.io)
2026-02-24 23:28:51 -05:00
Dante
9108b7535a feat: support video file drag-drop and paste (#9154) 2026-02-25 07:59:26 +09:00
Dante
02a38110cd feat: audio drag-drop and paste support (#9152) 2026-02-24 18:59:57 +09:00
Hunter
0f455c73bb fix: sync DOM widget values to widgetValueStore on registration (#9166)
## Summary

Override `setNodeId` in `BaseDOMWidgetImpl` to sync the DOM-resolved
value into the widget value store, fixing empty system prompts in Vue
nodes (Nodes 2.0).

## Changes

- **What**: DOM widgets (e.g. textarea for Gemini system_prompt) resolve
their value through `options.getValue()` / DOM elements, not
`_state.value`. When `BaseWidget.setNodeId` registers with the store, it
spreads `_state.value` which is `undefined` for DOM widgets. The
override captures the DOM-resolved value before registration and syncs
it into the store afterward — keeping the fix in the DOM widget layer
where the mismatch originates, leaving `BaseWidget` unchanged.

## Review Focus

- Whether capturing `this.value` before `super.setNodeId()` and writing
it after is the right sequencing
- Whether this correctly handles all DOM widget subtypes
(`DOMWidgetImpl`, `ComponentWidgetImpl`)

Supersedes #9164

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9166-fix-sync-DOM-widget-values-to-widgetValueStore-on-registration-3116d73d3650816f8cece866a9272baa)
by [Unito](https://www.unito.io)
2026-02-24 01:05:44 -08:00
Terry Jia
e333ad459e feat: add CurveEditor component (#8860)
## Summary
Prerequisite for upcoming native color correction nodes (ColorCurves).

Reusable curve editor with monotone cubic Hermite interpolation,
drag-to-add/move/delete control points, and SVG-based rendering.
Includes CurvePoint type, LUT generation utility, and useCurveEditor
composable for interaction logic.

## Screenshots (if applicable)



https://github.com/user-attachments/assets/948352c7-bdf2-40f9-a8f0-35bc2b2f3202

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8860-feat-add-CurveEditor-component-and-d3-shape-dependency-3076d73d3650817f8421f98e349569d0)
by [Unito](https://www.unito.io)
2026-02-23 21:19:06 -08:00
Johnpaul Chiwetelu
aab09b5f99 feat: allow custom event names in ComfyApi.addEventListener (#9140)
## Summary
- Add string fallback overloads to `addEventListener` and
`removeEventListener` on `ComfyApi`
- Extensions can now listen for custom event names without TypeScript
rejecting unknown event names
- Known events still get full type safety via the generic overload;
unknown strings fall through to `CustomEvent`

## Related Issue
- Fixes #2088

## QA
- Verify existing typed event listeners (e.g.
`api.addEventListener('status', ...)`) still infer correct types
- Verify custom event names (e.g.
`api.addEventListener('my-custom-event', ...)`) compile without errors

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9140-feat-allow-custom-event-names-in-ComfyApi-addEventListener-3116d73d36508128aad3fab98c34fac3)
by [Unito](https://www.unito.io)
2026-02-23 21:17:35 -08:00
Johnpaul Chiwetelu
1f0ca18737 fix: refresh image previews on media upload nodes when refreshing node definitions (#9141)
## Summary
- When pressing `R` to refresh node definitions, image previews on
LoadImage/LoadVideo nodes now update to reflect external file changes
- Re-triggers the combo widget callback to regenerate preview URLs with
a fresh cache-busting `&rand=` parameter
- Extracts `isMediaUploadComboInput` from `uploadImage.ts` to
`nodeDefSchema.ts` as a shared utility

- Fixes #2082



https://github.com/user-attachments/assets/d18d69ae-6ecd-448d-8d7c-76b2c49fdea5



## Test plan
- [ ] Open a workflow with a LoadImage node and select an image
- [ ] Edit and save the image externally (e.g. in an image editor)
- [ ] Press `R` to refresh node definitions
- [ ] Verify the preview updates to show the edited image
2026-02-23 20:57:24 -08:00
Alexander Brown
c25f9a0e93 feat: synthetic widgets getter for SubgraphNode (proxy-widget-v2) (#8856)
## Summary

Replace the Proxy-based proxy widget system with a store-driven
architecture where `promotionStore` and `widgetValueStore` are the
single sources of truth for subgraph widget promotion and widget values,
and `SubgraphNode.widgets` is a synthetic getter composing lightweight
`PromotedWidgetView` objects from store state.

## Motivation

The subgraph widget promotion system previously scattered state across
multiple unsynchronized layers:

- **Persistence**: `node.properties.proxyWidgets` (tuples on the
LiteGraph node)
- **Runtime**: Proxy-based `proxyWidget.ts` with `Overlay` objects,
`DisconnectedWidget` singleton, and `isProxyWidget` type guards
- **UI**: Each Vue component independently calling `parseProxyWidgets()`
via `customRef` hacks
- **Mutation flags**: Imperative `widget.promoted = true/false` set on
`subgraph-opened` events

This led to 4+ independent parsings of the same data, complex cache
invalidation, and no reactive contract between the promotion state and
the rendering layer. Widget values were similarly owned by LiteGraph
with no Vue-reactive backing.

The core principle driving these changes: **Vue owns truth**. Pinia
stores are the canonical source; LiteGraph objects delegate to stores
via getters/setters; Vue components react to store state directly.

## Changes

### New stores (single sources of truth)

- **`promotionStore`** — Reactive `Map<NodeId, PromotionEntry[]>`
tracking which interior widgets are promoted on which SubgraphNode
instances. Graph-scoped by root graph ID to prevent cross-workflow state
collision. Replaces `properties.proxyWidgets` parsing, `customRef`
hacks, `widget.promoted` mutation, and the `subgraph-opened` event
listener.
- **`widgetValueStore`** — Graph-scoped `Map<WidgetKey, WidgetState>`
that is the canonical owner of widget values. `BaseWidget.value`
delegates to this store via getter/setter when a node ID is assigned.
Eliminates the need for Proxy-based value forwarding.

### Synthetic widgets getter (SubgraphNode)

`SubgraphNode.widgets` is now a getter that reads
`promotionStore.getPromotions(rootGraphId, nodeId)` and returns cached
`PromotedWidgetView` objects. No stubs, no Proxies, no fake widgets
persisted in the array. The setter is a no-op — mutations go through
`promotionStore`.

### PromotedWidgetView

A class behind a `createPromotedWidgetView` factory, implementing the
`PromotedWidgetView` interface. Delegates value/type/options/drawing to
the resolved interior widget and stores. Owns positional state (`y`,
`computedHeight`) for canvas layout. Cached by
`PromotedWidgetViewManager` for object-identity stability across frames.

### DOM widget promotion

Promoted DOM widgets (textarea, image upload, etc.) render on the
SubgraphNode surface via `positionOverride` in `domWidgetStore`.
`DomWidgets.vue` checks for overrides and uses the SubgraphNode's
coordinates instead of the interior node's.

### Promoted previews

New `usePromotedPreviews` composable resolves image/audio/video preview
widgets from promoted entries, enabling SubgraphNodes to display
previews of interior preview nodes.

### Deleted

- `proxyWidget.ts` (257 lines) — Proxy handler, `Overlay`,
`newProxyWidget`, `isProxyWidget`
- `DisconnectedWidget.ts` (39 lines) — Singleton Proxy target
- `useValueTransform.ts` (32 lines) — Replaced by store delegation

### Key architectural changes

- `BaseWidget.value` getter/setter delegates to `widgetValueStore` when
node ID is set
- `LGraph.add()` reordered: `node.graph` assigned before widget
`setNodeId` (enables store registration)
- `LGraph.clear()` cleans up graph-scoped stores to prevent stale
entries across workflow switches
- `promotionStore` and `widgetValueStore` state nested under root graph
UUID for multi-workflow isolation
- `SubgraphNode.serialize()` writes promotions back to
`properties.proxyWidgets` for persistence compatibility
- Legacy `-1` promotion entries resolved and migrated on first load with
dev warning

## Test coverage

- **3,700+ lines of new/updated tests** across 36 test files
- **Unit**: `promotionStore.test.ts`, `widgetValueStore.test.ts`,
`promotedWidgetView.test.ts` (921 lines),
`subgraphNodePromotion.test.ts`, `proxyWidgetUtils.test.ts`,
`DomWidgets.test.ts`, `PromotedWidgetViewManager.test.ts`,
`usePromotedPreviews.test.ts`, `resolvePromotedWidget.test.ts`,
`subgraphPseudoWidgetCache.test.ts`
- **E2E**: `subgraphPromotion.spec.ts` (622 lines) — promote/demote,
manual/auto promotion, paste preservation, seed control augmentation,
image preview promotion; `imagePreview.spec.ts` extended with
multi-promoted-preview coverage
- **Fixtures**: 2 new subgraph workflow fixtures for preview promotion
scenarios

## Review focus

- Graph-scoped store keying (`rootGraphId`) — verify isolation across
workflows/tabs and cleanup on `LGraph.clear()`
- `PromotedWidgetView` positional stability — `_arrangeWidgets` writes
to `y`/`computedHeight` on cached objects; getter returns fresh array
but stable object references
- DOM widget position override lifecycle — overrides set on promote,
cleared on demote/removal/subgraph navigation
- Legacy `-1` entry migration — resolved and written back on first load;
unresolvable entries dropped with dev warning
- Serialization round-trip — `promotionStore` state →
`properties.proxyWidgets` on serialize, hydrated back on configure

## Diff breakdown (excluding lockfile)

- 153 files changed, ~7,500 insertions, ~1,900 deletions (excluding
pnpm-lock.yaml churn)
- ~3,700 lines are tests
- ~300 lines deleted (proxyWidget.ts, DisconnectedWidget.ts,
useValueTransform.ts)

<!-- Fixes #ISSUE_NUMBER -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8856-feat-synthetic-widgets-getter-for-SubgraphNode-proxy-widget-v2-3076d73d365081c7b517f5ec7cb514f3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-23 13:33:41 -08:00
Johnpaul Chiwetelu
02e926471f fix: replace as-unknown-as casts with safer patterns (#9107)
## Summary

- Replace 83 `as unknown as` double casts with safer alternatives across
33 files
- Use `as Partial<X> as X` pattern where TypeScript allows it
- Create/reuse factory functions from `litegraphTestUtils.ts` for mock
objects
- Widen `getWorkflowDataFromFile` return type to include `ComfyMetadata`
directly
- Reduce total `as unknown as` count from ~153 to 71

The remaining 71 occurrences are genuinely necessary due to cross-schema
casts, generic variance, missing index signatures, Float64Array-to-tuple
conversions, and DOM type incompatibilities.

## Test plan

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] All affected unit tests pass

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9107-fix-replace-as-unknown-as-casts-with-safer-patterns-3106d73d3650815cb5bcd613ad635bd7)
by [Unito](https://www.unito.io)
2026-02-22 20:46:12 -08:00
Dante
38675e658f feat: add dev-time feature flag overrides via localStorage (#9075)
## Summary
- Adds `localStorage`-based dev-time override for feature flags, with
`ff:` key prefix (e.g.
`localStorage.setItem('ff:team_workspaces_enabled', 'true')`)
- Override priority: dev localStorage > remoteConfig >
serverFeatureFlags
- Guarded by `import.meta.env.DEV` — tree-shaken to empty function in
production builds
- Extracts `resolveFlag` helper in `useFeatureFlags` to eliminate
repeated fallback pattern

Fixes #9054

## Test plan
- [x] `getDevOverride` unit tests: boolean/number/string/object parsing,
prefix isolation, invalid JSON warning
- [x] `api.getServerFeature` / `serverSupportsFeature` override tests
- [x] `useFeatureFlags` override priority tests, including
`teamWorkspacesEnabled` bypassing guards
- [x] Production build verified: `getDevOverride` compiles to empty
function body, localStorage never accessed
- [x] `pnpm typecheck`, `pnpm lint` clean

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9075-feat-add-dev-time-feature-flag-overrides-via-localStorage-30f6d73d365081b394d3ccc461987b1a)
by [Unito](https://www.unito.io)
2026-02-21 20:36:09 -08:00
jaeone94
8aa4e36fd5 [refactor] Extract executionErrorStore from executionStore (#9060)
## Summary
Extracts error-related state and logic from `executionStore` into a
dedicated `executionErrorStore` for better separation of concerns.

## Changes
- **New store**: `executionErrorStore` with all error state
(`lastNodeErrors`, `lastExecutionError`, `lastPromptError`), computed
properties (`hasAnyError`, `totalErrorCount`,
`activeGraphErrorNodeIds`), and UI state (`isErrorOverlayOpen`,
`showErrorOverlay`, `dismissErrorOverlay`)
- **Moved util**: `executionIdToNodeLocatorId` extracted to
`graphTraversalUtil`, reusing `traverseSubgraphPath` and accepting
`rootGraph` as parameter
- **Updated consumers**: 12 files updated to import from
`executionErrorStore`
- **Backward compat**: Deprecated getters retained in `ComfyApp` for
extension compatibility

## Review Focus
- Deprecated getters in `app.ts` — can be removed in a future
breaking-change PR once extension authors migrate

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9060-refactor-Extract-executionErrorStore-from-executionStore-30e6d73d36508101973de835ab6b199f)
by [Unito](https://www.unito.io)
2026-02-21 16:51:22 -08:00
Christian Byrne
d9fdb01d9b fix: handle failed global subgraph blueprint loading gracefully (#9063)
## Summary

Fix "Failed to load subgraph blueprints Error: [ASSERT] Workflow content
should be loaded" error occurring on cloud.

## Changes

- **What**: `getGlobalSubgraphData` now throws on API failure instead of
returning empty string, global blueprint data is validated before
loading, and individual global blueprint errors are properly propagated
to the toast/console reporting instead of being silently swallowed.

## Review Focus

Two root causes were fixed:
1. `getGlobalSubgraphData` returned `""` on failure — this empty string
was set as `originalContent`, which is falsy, triggering the assertion
in `ComfyWorkflow.load()`.
2. `loadInstalledBlueprints` used an internal `Promise.allSettled` whose
results were discarded, so individual global blueprint failures never
reached the error reporting in `fetchSubgraphs`.

Fixes COM-15199

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9063-fix-handle-failed-global-subgraph-blueprint-loading-gracefully-30e6d73d3650818d9cc8ecf81cd0264e)
by [Unito](https://www.unito.io)
2026-02-21 16:30:01 -08:00
Dante
de131133bd fix: make serverFeatureFlags reactive via Vue ref (#9051) 2026-02-21 18:43:58 +09:00
Jin Yi
2b69d7b49c [bugfix] Fix node replacements not loading due to feature flag timing (#9037)
## Summary
- Node replacements were never loaded because
`useNodeReplacementStore().load()` was called before `api.init()`,
meaning `serverFeatureFlags` was always empty at that point
- Dispatch `feature_flags` as a custom event from `api.ts` and trigger
`load()` in response within `addApiUpdateHandlers()`

## Changes
- **`api.ts`**: Dispatch `feature_flags` custom event after storing
server feature flags (already typed in `BackendApiCalls`)
- **`app.ts`**: Replace eager `load()` call with `feature_flags` event
listener inside `addApiUpdateHandlers()`, consistent with other API
event handlers
- **`nodeReplacementStore.ts`**: Use `api.getServerFeature()` directly
instead of `useFeatureFlags` composable; remove side effects from store
setup
- **`nodeReplacementStore.test.ts`**: Update mocks to match new
`api.getServerFeature` usage

## Review Focus
- Initialization ordering: listener registered before `api.init()`
ensures no missed events

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9037-bugfix-Fix-node-replacements-not-loading-due-to-feature-flag-timing-30e6d73d36508107ae2cd72e83c01e1a)
by [Unito](https://www.unito.io)
2026-02-20 19:16:01 -08:00
Christian Byrne
337e0486ea feat: client-side distribution filtering for blueprint subgraphs (#8686)
Adds client-side filtering of blueprint subgraphs by distribution.

**Changes:**
- Added `includeOnDistributions` typed field to `GlobalSubgraphData` in
`api.ts`
- Distribution detection: `isCloud → 'cloud'`, `isDesktop → 'desktop'`,
else `'localhost'`
- Filters subgraphs before loading — excluded blueprints are never
fetched

**Depends on:** Comfy-Org/workflow_templates schema update (merge first)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8686-feat-client-side-distribution-filtering-for-blueprint-subgraphs-2ff6d73d365081d29f79c4e3cab174ac)
by [Unito](https://www.unito.io)
2026-02-20 17:02:54 -08:00
jaeone94
46c40c755e feat: node-specific error tab with selection-aware grouping and error overlay (#8956)
## Summary
Enhances the error panel with node-specific views: single-node selection
shows errors grouped by message in compact mode, container nodes
(subgraph/group) expose child errors via a badge and "See Error" button,
and a floating ErrorOverlay appears after execution failure with a
deduplicated summary and quick navigation to the errors tab.

## Changes
- **Consolidate error tab**: Remove `TabError.vue`; merge all error
display into `TabErrors.vue` and drop the separate `error` tab type from
`rightSidePanelStore`
- **Selection-aware grouping**: Single-node selection regroups errors by
message (not `class_type`) and renders `ErrorNodeCard` in compact mode
- **Container node support**: Detect child-node errors in subgraph/group
nodes via execution ID prefix matching; show error badge and "See Error"
button in `SectionWidgets`
- **ErrorOverlay**: New floating card shown after execution failure with
deduplicated error messages, "Dismiss" and "See Errors" actions;
`isErrorOverlayOpen` / `showErrorOverlay` / `dismissErrorOverlay` added
to `executionStore`
- **Refactor**: Centralize error ID collection in `executionStore`
(`allErrorExecutionIds`, `hasInternalErrorForNode`); split `errorGroups`
into `allErrorGroups` (unfiltered) and `tabErrorGroups`
(selection-filtered); move `ErrorOverlay` business logic into
`useErrorGroups`

## Review Focus
- `useErrorGroups.ts`: split into `allErrorGroups` / `tabErrorGroups`
and the new `filterBySelection` parameter flow
- `executionStore.ts`: `hasInternalErrorForNode` helper and
`allErrorExecutionIds` computed
- `ErrorOverlay.vue`: integration with `executionStore` overlay state
and `useErrorGroups`

## Screenshots
<img width="853" height="461" alt="image"
src="https://github.com/user-attachments/assets/a49ab620-4209-4ae7-b547-fba13da0c633"
/>
<img width="854" height="203" alt="image"
src="https://github.com/user-attachments/assets/c119da54-cd78-4e7a-8b7a-456cfd348f1d"
/>
<img width="497" height="361" alt="image"
src="https://github.com/user-attachments/assets/74b16161-cf45-454b-ae60-24922fe36931"
/>

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-20 12:14:52 -08:00
Christian Byrne
473713cf02 refactor: rename internal promptId/PromptId to jobId/JobId (#8730)
## Summary

Rename all internal TypeScript usage of legacy `promptId`/`PromptId`
naming to `jobId`/`JobId` across ~38 files for consistency with the
domain model.

## Changes

- **What**: Renamed internal variable names, type aliases, function
names, class getters, interface fields, and comments from
`promptId`/`PromptId` to `jobId`/`JobId`. Wire-protocol field names
(`prompt_id` in Zod schemas and `e.detail.prompt_id` accesses) are
intentionally preserved since they match the backend API contract.

## Review Focus

- All changes are pure renames with no behavioral changes
- Wire-protocol fields (`prompt_id`) are deliberately unchanged to
maintain backend compatibility
- Test fixtures updated to use consistent `job-id` naming

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8730-refactor-rename-internal-promptId-PromptId-to-jobId-JobId-3016d73d3650813ca40ce337f7c5271a)
by [Unito](https://www.unito.io)
2026-02-20 02:10:53 -08:00
pythongosssss
6902e38e6a V2 Node Search (+ hidden Node Library changes) (#8987)
## Summary

Redesigned node search with categories

## Changes

- **What**: Adds a v2 search component, leaving the existing
implementation untouched
- It also brings onboard the incomplete node library & preview changes,
disabled and behind a hidden setting
- **Breaking**: Changes the 'default' value of the node search setting
to v2, adding v1 (legacy) as an option

## Screenshots (if applicable)




https://github.com/user-attachments/assets/2ab797df-58f0-48e8-8b20-2a1809e3735f

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8987-V2-Node-Search-hidden-Node-Library-changes-30c6d73d36508160902bcb92553f147c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Yourz <crazilou@vip.qq.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2026-02-20 01:10:03 -08:00
Christian Byrne
7c4486ed29 feat: automatically fit view when loading templates (#8749)
## Description

When loading a template workflow, always call `fitView()` to ensure the
template is properly framed within the viewport. This provides a better
initial viewing experience for users.

## Problem

Previously, templates with embedded viewport positions (`extra.ds`)
would load at arbitrary positions, sometimes showing a blank canvas.
Users had to navigate significantly to find the workflow content.

## Solution

Added a single condition in `loadGraphData()` to check if `openSource
=== 'template'` and force `fitView()` when true, bypassing the saved
viewport position.

This change only affects template loading - normal workflow loading
behavior remains unchanged.

## Changes

- `src/scripts/app.ts`: Added condition to always call `fitView()` when
loading templates

## Testing

- Load various templates from the template selector
- Verify the workflow is fully visible and centered on load
- Verify normal workflow loading still respects saved positions

## Related

- Fixes COM-14156
- Related to COM-10087 (Center view on workflow when loading templates -
Done)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8749-feat-automatically-fit-view-when-loading-templates-3016d73d36508167a63de2ae407de7b8)
by [Unito](https://www.unito.io)
2026-02-20 00:37:38 -08:00
Jin Yi
44733f010d [refactor] Unify small modal dialog styles with showSmallLayoutDialog (#8834)
## Summary
Extract a shared `showSmallLayoutDialog` utility and move
dialog-specific logic into composables, unifying the duplicated `pt`
configurations across small modal dialogs.

## Changes
- **`showSmallLayoutDialog`**: Added to `dialogService.ts` with a single
unified `pt` config for all small modal dialogs (missing nodes, missing
models, import failed, node conflict)
- **Composables**: Extracted 4 dialog functions from `dialogService`
into dedicated composables following the `useSettingsDialog` /
`useModelSelectorDialog` pattern:
  - `useMissingNodesDialog`
  - `useMissingModelsDialog`
  - `useImportFailedNodeDialog`
  - `useNodeConflictDialog`
- Each composable uses direct imports, synchronous `show()`, `hide()`,
and a `DIALOG_KEY` constant
- Updated all call sites (`app.ts`, `useHelpCenter`, `PackEnableToggle`,
`PackInstallButton`, `useImportFailedDetection`)

## Review Focus
- Unified `pt` config removes minor style variations between dialogs —
intentional design unification

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8834-refactor-Unify-small-modal-dialog-styles-with-showSmallLayoutDialog-3056d73d365081b6963beffc0e5943bf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-19 20:58:59 -08:00
Johnpaul Chiwetelu
25696ffe03 fix: remove false 'invalid directory' errors when asset API is enabled (#8961)
## Summary
- When `Comfy.Assets.UseAssetAPI` is enabled, `getAssetModelFolders()`
only discovers folders containing assets. Empty folders (e.g.
`text_encoders`, `vae`) were falsely flagged as invalid, showing
"Invalid directory specified" on every missing model dialog.
- Removes the `directory_invalid` concept entirely — the existing
`!paths` check via `getFolderPaths()` already correctly validates all
registered directories including empty ones, making `directory_invalid`
redundant.

## Before
<img width="1841" height="954" alt="Screenshot 2026-02-18 at 21 09 55"
src="https://github.com/user-attachments/assets/09cf4f28-5175-4ff6-aa9d-916568c6d9b3"
/>


## After
<img width="1134" height="738" alt="Screenshot 2026-02-18 at 21 23 29"
src="https://github.com/user-attachments/assets/578d2fa5-3fb8-401a-beee-0fd74667f08b"
/>

## Test plan
- [ ] Enable `Comfy.Assets.UseAssetAPI` setting
- [ ] Open a template referencing models in empty folders (e.g. "Image
Editing (New)")
- [ ] Verify the missing models dialog shows download buttons instead of
"Invalid directory specified" error
- [ ] Disable `Comfy.Assets.UseAssetAPI` and verify behavior is
unchanged

Fixes #8583
2026-02-19 20:51:21 -08:00
Alexander Brown
8099cce232 feat: bulk asset export with ZIP download (#8712)
## Summary

Adds bulk asset export with ZIP download for cloud users. When selecting
2+ assets and clicking download, the frontend now requests a server-side
ZIP export instead of triggering individual file downloads.

## Changes

### New files
- **`AssetExportProgressDialog.vue`** — HoneyToast-based progress dialog
showing per-job export status with progress percentages, error
indicators, and a manual re-download button for completed exports
- **`assetExportStore.ts`** — Pinia store that tracks export jobs,
handles `asset_export` WebSocket events for real-time progress, polls
stale exports via the task API as a fallback, and auto-triggers ZIP
download on completion

### Modified files
- **`useMediaAssetActions.ts`** — `downloadMultipleAssets` now routes to
ZIP export (via `createAssetExport`) in cloud mode when 2+ assets are
selected; single assets and OSS mode still use direct download
- **`assetService.ts`** — Added `createAssetExport()` and
`getExportDownloadUrl()` endpoints
- **`apiSchema.ts`** — Added `AssetExportWsMessage` type for the
WebSocket event
- **`api.ts`** — Wired up `asset_export` WebSocket event
- **`GraphView.vue`** — Mounted `AssetExportProgressDialog`
- **`main.json`** — Added i18n keys for export toast UI

## How it works

1. User selects multiple assets and clicks download
2. Frontend calls `POST /assets/export` with asset/job IDs
3. Backend creates a ZIP task and streams progress via `asset_export`
WebSocket events
4. `AssetExportProgressDialog` shows real-time progress
5. On completion, the ZIP is auto-downloaded via a presigned URL from
`GET /assets/exports/{name}`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8712-feat-bulk-asset-export-with-ZIP-download-3006d73d365081839ec3dd3e7b0d3b77)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-18 16:36:59 -08:00
jaeone94
1349fffbce Feat/errors tab panel (#8807)
## Summary

Add a dedicated **Errors tab** to the Right Side Panel that displays
prompt-level, node validation, and runtime execution errors in a
unified, searchable, grouped view — replacing the need to rely solely on
modal dialogs for error inspection.

## Changes

- **What**:
  - **New components** (`errors/` directory):
- `TabErrors.vue` — Main error tab with search, grouping by class type,
and canvas navigation (locate node / enter subgraph).
- `ErrorNodeCard.vue` — Renders a single error card with node ID badge,
title, action buttons, and error details.
- `types.ts` — Shared type definitions (`ErrorItem`, `ErrorCardData`,
`ErrorGroup`).
- **`executionStore.ts`** — Added `PromptError` interface,
`lastPromptError` ref and `hasAnyError` computed getter. Clears
`lastPromptError` alongside existing error state on execution start and
graph clear.
- **`rightSidePanelStore.ts`** — Registered `'errors'` as a valid tab
value.
- **`app.ts`** — On prompt submission failure (`PromptExecutionError`),
stores prompt-level errors (when no node errors exist) into
`lastPromptError`. On both runtime execution error and prompt error,
deselects all nodes and opens the errors tab automatically.
- **`RightSidePanel.vue`** — Shows the `'errors'` tab (with ⚠ icon) when
errors exist and no node is selected. Routes to `TabErrors` component.
- **`TopMenuSection.vue`** — Highlights the action bar with a red border
when any error exists, using `hasAnyError`.
- **`SectionWidgets.vue`** — Detects per-node errors by matching
execution IDs to graph node IDs. Shows an error icon (⚠) and "See Error"
button that navigates to the errors tab.
- **`en/main.json`** — Added i18n keys: `errors`, `noErrors`,
`enterSubgraph`, `seeError`, `promptErrors.*`, and `errorHelp*`.
- **Testing**: 6 unit tests (`TabErrors.test.ts`) covering
prompt/node/runtime errors, search filtering, and clipboard copy.
- **Storybook**: 7 stories (`ErrorNodeCard.stories.ts`) for badge
visibility, subgraph buttons, multiple errors, runtime tracebacks, and
prompt-only errors.
- **Breaking**: None
- **Dependencies**: None — uses only existing project dependencies
(`vue-i18n`, `pinia`, `primevue`)

## Related Work

> **Note**: Upstream PR #8603 (`New bottom button and badges`)
introduced a separate `TabError.vue` (singular) that shows per-node
errors when a specific node is selected. Our `TabErrors.vue` (plural)
provides the **global error overview** — a different scope. The two tabs
coexist:
> - `'error'` (singular) → appears when a node with errors is selected →
shows only that node's errors
> - `'errors'` (plural) → appears when no node is selected and errors
exist → shows all errors grouped by class type
>
> A future consolidation of these two tabs may be desirable after design
review.

## Architecture

```
executionStore
├── lastPromptError: PromptError | null     ← NEW (prompt-level errors without node IDs)
├── lastNodeErrors: Record<string, NodeError>  (existing)
├── lastExecutionError: ExecutionError         (existing)
└── hasAnyError: ComputedRef<boolean>       ← NEW (centralized error detection)

TabErrors.vue (errors tab - global view)
├── errorGroups: ComputedRef<ErrorGroup[]>  ← normalizes all 3 error sources
├── filteredGroups                          ← search-filtered view
├── locateNode()                            ← pan canvas to node
├── enterSubgraph()                         ← navigate into subgraph
└── ErrorNodeCard.vue                       ← per-node card with copy/locate actions

types.ts
├── ErrorItem      { message, details?, isRuntimeError? }
├── ErrorCardData  { id, title, nodeId?, errors[] }
└── ErrorGroup     { title, cards[], priority }
```

## Review Focus

1. **Error normalization logic** (`TabErrors.vue` L75–150): Three
different error sources (prompt, node validation, runtime) are
normalized into a common `ErrorGroup → ErrorCardData → ErrorItem`
hierarchy. Edge cases to verify:
- Prompt errors with known vs unknown types (known types use localized
descriptions)
   - Multiple errors on the same node (grouped into one card)
   - Runtime errors with long tracebacks (capped height with scroll)

2. **Canvas navigation** (`TabErrors.vue` L210–250): The `locateNode`
and `enterSubgraph` functions navigate to potentially nested subgraphs.
The double `requestAnimationFrame` is required due to LiteGraph's
asynchronous subgraph switching — worth verifying this timing is
sufficient.

3. **Store getter consolidation**: `hasAnyError` replaces duplicated
logic in `TopMenuSection` and `RightSidePanel`. Confirm that the
reactive dependency chain works correctly (it depends on 3 separate
refs).

4. **Coexistence with upstream `TabError.vue`**: The singular `'error'`
tab (upstream, PR #8603) and our plural `'errors'` tab serve different
purposes but share similar naming. Consider whether a unified approach
is preferred.

## Test Results

```
✓ renders "no errors" state when store is empty
✓ renders prompt-level errors (Group title = error message)
✓ renders node validation errors grouped by class_type
✓ renders runtime execution errors from WebSocket
✓ filters errors based on search query
✓ calls copyToClipboard when copy button is clicked

Test Files  1 passed (1)
     Tests  6 passed (6)
```

## Screenshots (if applicable)
<img width="1238" height="1914" alt="image"
src="https://github.com/user-attachments/assets/ec39b872-cca1-4076-8795-8bc7c05dc665"
/>
<img width="669" height="1028" alt="image"
src="https://github.com/user-attachments/assets/bdcaa82a-34b0-46a5-a08f-14950c5a479b"
/>
<img width="644" height="1005" alt="image"
src="https://github.com/user-attachments/assets/ffef38c6-8f42-4c01-a0de-11709d54b638"
/>
<img width="672" height="505" alt="image"
src="https://github.com/user-attachments/assets/5cff7f57-8d79-4808-a71e-9ad05bab6e17"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8807-Feat-errors-tab-panel-3046d73d36508127981ac670a70da467)
by [Unito](https://www.unito.io)
2026-02-17 21:01:15 -08:00
pythongosssss
f5f5a77435 Add support for dragging in multiple workflow files at once (#8757)
## Summary

Allows users to drag in multiple files that are/have embedded workflows
and loads each of them as tabs.
Previously it would only load the first one.

## Changes

- **What**: 
- process all files from drop event
- add defered errors so you don't get errors for non-visible workflows

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8757-Add-support-for-dragging-in-multiple-workflow-files-at-once-3026d73d365081c096e9dfb18ba01253)
by [Unito](https://www.unito.io)
2026-02-16 23:45:22 -08:00
Jin Yi
efe78b799f [feat] Node replacement UI (#8604)
## Summary
Add node replacement UI to the missing nodes dialog. Users can select
and replace deprecated/missing nodes with compatible alternatives
directly from the dialog.

## Changes
- Classify missing nodes into **Replaceable** (quick fix) and **Install
Required** sections
- Add select-all checkbox + per-node checkboxes for batch replacement
- `useNodeReplacement` composable handles in-place node replacement on
the graph:
  - Simple replacement (configure+copy) for nodes without mapping
  - Input/output connection remapping for nodes with mapping
  - Widget value transfer via `old_widget_ids`
  - Dot-notation input handling for Autogrow/DynamicCombo
  - Undo/redo support via `changeTracker` (try/finally)
  - Title and properties preservation
- Footer UX: "Skip for Now" button when all nodes are replaceable (cloud
+ OSS)
- Auto-close dialog when all replaceable nodes are replaced and no
non-replaceable remain
- Settings navigation link from "Don't show again" checkbox
- 505-line unit test suite for `useNodeReplacement`

## Review Focus
- `useNodeReplacement.ts` — core graph manipulation logic
- `MissingNodesContent.vue` — checkbox selection state management
- `MissingNodesFooter.vue` — conditional button rendering (cloud vs OSS
vs all-replaceable)


[screen-capture.webm](https://github.com/user-attachments/assets/7dae891c-926c-4f26-987f-9637c4a2ca16)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8604-feat-Node-replacement-UI-2fd6d73d36508148a371dabb8f4115af)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-16 23:33:41 -08:00
Christian Byrne
066a1f1f11 fix: drag-and-drop screenshot creates LoadImage node instead of showing error (#8886)
## Summary

Fix drag-and-drop of local screenshots onto the canvas failing to create
a LoadImage node.

## Changes

- **What**: Replace `_.isEmpty(workflowData)` check in `handleFile` with
a check for workflow-relevant keys (`workflow`, `prompt`, `parameters`,
`templates`). PNG screenshots often contain non-workflow `tEXt` metadata
(e.g. `Software`, `Creation Time`) which made `_.isEmpty()` return
`false`, skipping the LoadImage fallback and showing an error instead.

## Review Focus

The root cause is that `getPngMetadata` extracts all `tEXt`/`iTXt` PNG
chunks indiscriminately. Rather than filtering at the parser level
(which could break extensions relying on arbitrary metadata), the fix
checks for workflow-relevant keys before deciding whether to treat the
file as a workflow or a plain image.

Fixes #7752

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8886-fix-drag-and-drop-screenshot-creates-LoadImage-node-instead-of-showing-error-3086d73d3650817d86c5f1386aa041c2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-14 22:25:59 -08:00
Jin Yi
96b9e886ea feat: classify missing nodes by replacement availability (#8483)
## Summary
- Extend `MissingNodeType` with `isReplaceable` and `replacement` fields
- Classify missing nodes by checking
`nodeReplacementStore.getReplacementFor()` during graph load
- Wrap hardcoded node patches (T2IAdapterLoader, ConditioningAverage,
etc.) in `if (!isEnabled)` guard so they only run when node replacement
setting is disabled
- Change `useNodeReplacementStore().load()` from fire-and-forget
(`void`) to `await` so replacement data is available before missing node
detection
- Fix guard condition order in `nodeReplacementStore.load()`: check
`isEnabled` before `isLoaded`
- Align `InputMap` types with actual API response (flat
`old_id`/`set_value` fields instead of nested `assign` wrapper)

## Test plan
- [x] Load workflow with deprecated nodes (T2IAdapterLoader,
Load3DAnimation, SDV_img2vid_Conditioning)
- [x] Verify missing nodes are classified with `isReplaceable: true` and
`replacement` object
- [x] Verify hardcoded patches only run when node replacement setting is
OFF
- [x] Verify `nodeReplacementStore.load()` is not called when setting is
disabled
- [x] Unit tests pass (`nodeReplacementStore.test.ts` - 16 tests)
- [x] Typecheck passes

🤖 Generated with [Claude Code](https://claude.ai/code)
2026-02-15 11:26:46 +09:00
Benjamin Lu
fcb4341c98 feat(queue): introduce queue notification banners and remove completion summary flow (#8740)
## Summary
Replace the old completion-summary overlay path with queue notification
banners for queueing/completed/failed lifecycle feedback.

## Key changes
- Added `QueueNotificationBanner`, `QueueNotificationBannerHost`,
stories, and tests.
- Added `useQueueNotificationBanners` to handle:
  - immediate `queuedPending` on `promptQueueing`
  - transition to `queued` on `promptQueued` (request-id aware)
  - completed/failed notification sequencing from finished batch history
  - timed notification queueing/dismissal
- Removed completion-summary implementation:
  - `useCompletionSummary`
  - `CompletionSummaryBanner`
  - `QueueOverlayEmpty`
- Simplified `QueueProgressOverlay` to `hidden | active | expanded`
states.
- Top menu behavior:
  - restored `QueueInlineProgressSummary` as separate UI
  - ordering is inline summary first, notification banner below
- notification banner remains under the top menu section (not teleported
to floating actionbar target)
- Kept established API-event signaling pattern
(`promptQueueing`/`promptQueued`) instead of introducing a separate bus.
- Updated tests for top-menu visibility/ordering and notification
behavior across QPOV2 enabled/disabled.

## Notes
- Completion notifications now support stacked thumbnails (cap: 3).
-
https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3843-20314&m=dev

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8740-feat-Queue-Notification-Toasts-3016d73d3650814c8a50d9567a40f44d)
by [Unito](https://www.unito.io)
2026-02-14 12:14:55 -08:00
Jin Yi
553ea63357 [refactor] Migrate SettingDialog to BaseModalLayout design system (#8270) 2026-02-12 16:27:11 +09:00
Alexander Brown
30ef6f2b8c Fix: Disabling textarea when linked. (#8818)
## Summary

Misaligned option setting when building the SimplifiedWidget.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8818-Fix-Disabling-textarea-when-linked-3056d73d365081f581a9f1322aaf60bd)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-11 18:45:52 -08:00
Brian Jemilo II
a80f6d7922 Batch Drag & Drop Images (#8282)
## Summary

<!-- One sentence describing what changed and why. -->
Added feature to drag and drop multiple images into the UI and connect
them with a Batch Images node with tests to add convenience for users.
Only works with a group of images, mixing files not supported.

## Review Focus
<!-- Critical design decisions or edge cases that need attention -->
I've updated our usage of Litegraph.createNode, honestly, that method is
pretty bad, onNodeCreated option method doesn't even return the node
created. I think I will probably go check out their repo to do a PR over
there. Anyways, I made a createNode method to avoid race conditions when
creating nodes for the paste actions. Will allow us to better
programmatically create nodes that do not have workflows that also need
to be connected to other nodes.

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

https://www.notion.so/comfy-org/Implement-Multi-image-drag-and-drop-to-canvas-2eb6d73d36508195ad8addfc4367db10

## Screenshots (if applicable)

https://github.com/user-attachments/assets/d4155807-56e2-4e39-8ab1-16eda90f6a53

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8282-Batch-Drag-Drop-Images-2f16d73d365081c1ab31ce9da47a7be5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Austin Mroz <austin@comfy.org>
2026-02-11 17:39:41 -08:00
Alexander Brown
a7c2115166 feat: add WidgetValueStore for centralized widget value management (#8594)
## Summary

Implements Phase 1 of the **Vue-owns-truth** pattern for widget values.
Widget values are now canonical in a Pinia store; `widget.value`
delegates to the store while preserving full backward compatibility.

## Changes

- **New store**: `src/stores/widgetValueStore.ts` - centralized widget
value storage with `get/set/remove/removeNode` API
- **BaseWidget integration**: `widget.value` getter/setter now delegates
to store when widget is associated with a node
- **LGraphNode wiring**: `addCustomWidget()` automatically calls
`widget.setNodeId(this.id)` to wire widgets to their nodes
- **Test fixes**: Added Pinia setup to test files that use widgets

## Why

This foundation enables:
- Vue components to reactively bind to widget values via `computed(() =>
store.get(...))`
- Future Yjs/CRDT backing for real-time collaboration
- Cleaner separation between Vue state and LiteGraph rendering

## Backward Compatibility

| Extension Pattern | Status |
|-------------------|--------|
| `widget.value = x` |  Works unchanged |
| `node.widgets[i].value` |  Works unchanged |
| `widget.callback` |  Still fires |
| `node.onWidgetChanged` |  Still fires |

## Testing

-  4252 unit tests pass
-  Build succeeds

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8594-feat-add-WidgetValueStore-for-centralized-widget-value-management-2fc6d73d36508160886fcb9f3ebd941e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-10 19:37:17 -08:00
Csongor Czezar
d044bed9b2 feat: use object info display_name as fallback before node name (#7622)
## Description
Implements fallback to object info display_name before using internal
node name when display_name is unavailable.

## Related Issue
Related to backend PR: comfyanonymous/ComfyUI#11340

## Changes
- Modified `getNodeDefs()` to use object info `display_name` before
falling back to `name`
- Added unit tests for display name fallback behavior

## Testing
- All existing tests pass
- Added 4 new unit tests covering various display_name scenarios

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7622-W-I-P-feat-use-object-info-display_name-as-fallback-before-node-name-2cd6d73d365081deb22fe5ed00e6dc2e)
by [Unito](https://www.unito.io)
2026-02-10 22:18:48 -05:00