Commit Graph

7778 Commits

Author SHA1 Message Date
Christian Byrne
fa1ffcba01 fix: handle clipboard errors in Copy Image and useCopyToClipboard (#9299)
## Summary

Fix unhandled promise rejection ("Document is not focused") in Copy
Image and improve clipboard fallback reliability.

## Changes

- **What**: Two clipboard fixes:
1. `litegraphService.ts`: The "Copy Image" context menu passed async
`writeImage` as a callback to `canvas.toBlob()` without awaiting —
errors became unhandled promise rejections reported in [Sentry
CLOUD-FRONTEND-STAGING-AQ](https://comfy-org.sentry.io/issues/6948073569/).
Extracted `convertToPngBlob` helper that wraps `toBlob` in a proper
Promise so errors propagate to the existing outer try/catch and surface
as a user-facing toast instead of a silent Sentry error.
2. `useCopyToClipboard.ts`: Replaced `useClipboard({ legacy: true })`
with explicit modern→legacy fallback that checks
`document.execCommand('copy')` return value. VueUse's `legacyCopy` sets
`copied.value = true` regardless of whether `execCommand` succeeded,
causing false success toasts.

## Review Focus

- The `convertToPngBlob` helper does the same canvas→PNG work as the old
inline code but properly awaited
- The happy path (PNG clipboard write succeeds first try) is unchanged
- No public API surface changes — verified zero custom node dependencies
via ecosystem code search

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9299-fix-handle-clipboard-errors-in-Copy-Image-and-useCopyToClipboard-3156d73d3650817c8608cba861ee64a9)
by [Unito](https://www.unito.io)
2026-03-25 12:20:33 -07:00
Christian Byrne
f1db1122f3 fix: allow URI drops to bubble from Vue nodes to document handler (#9463)
## Summary

Fix URI drops (e.g. dragging `<img>` thumbnails) onto Vue-rendered nodes
by letting unhandled drops bubble to the document-level `text/uri-list`
fallback in `app.ts`.

## Changes

- **What**: Removed unconditional `.stop` modifier from `@drop` in
`LGraphNode.vue`. `stopPropagation()` is now called conditionally — only
when `onDragDrop` returns `true` (file drop handled). Made `handleDrop`
synchronous since `onDragDrop` returns a plain boolean.

## Review Focus

The key insight is that `onDragDrop` (from `useNodeDragAndDrop`) returns
`false` synchronously for URI drags (no files in `DataTransfer`), so the
event must bubble to reach the document handler that fetches the URI.
The original `async` + `await` pattern would have deferred
`stopPropagation` past the synchronous propagation phase, so
`handleDrop` is now synchronous.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9463-fix-allow-URI-drops-to-bubble-from-Vue-nodes-to-document-handler-31b6d73d36508196a1b3f17e7e4837a9)
by [Unito](https://www.unito.io)
2026-03-25 12:19:38 -07:00
Christian Byrne
f56abb3ecf test: add regression tests for subgraph slot label propagation (#10013)
## Summary

Add regression tests for subgraph slot label propagation. The
OutputSlot.vue fix (adding `slotData.label` to the display template) was
already merged via another PR — this adds tests to prevent future
regressions.

## Changes

- **What**: Two new test files covering the label/localized_name
fallback chain in OutputSlot.vue and SubgraphNode label propagation
through configure() and rename event paths.

## Review Focus

Tests only — no production code changes. Verifies that renamed subgraph
inputs/outputs display correctly in Nodes 2.0 mode.

Fixes #9998

<!-- Pipeline-Ticket: 7d887122-eea5-45f1-b6eb-aed94f708555 -->
2026-03-25 12:19:08 -07:00
Benjamin Lu
95c6811f59 fix: remove unused Playwright hook config args (#10513)
## Summary

Remove the unused `_config` parameter from the Playwright global
setup/teardown hooks and drop the now-unused `FullConfig` imports.

## Changes

- **What**: Simplified `browser_tests/globalSetup.ts` and
`browser_tests/globalTeardown.ts` to match actual usage.

## Review Focus

Verify that removing the unused hook argument does not change Playwright
behavior.

## Screenshots (if applicable)

N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10513-fix-remove-unused-Playwright-hook-config-args-32e6d73d365081d59b63dbbca0596025)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-25 19:18:15 +00:00
Christian Byrne
88079250eb chore: add CI safety rules to backport-management skill (#10164)
Adds lessons learned from a bulk backport session where 69 PRs were
admin-merged without CI checks, shipping 3 test failures to core/1.41.

**Changes:**
- **SKILL.md**: CI Safety Rules section, wave verification with `pnpm
test:unit`, continuous backporting recommendation, Never Admin-Merge
Without CI lesson
- **execution.md**: Wait-for-CI step after automation, `gh pr checks
--watch` for manual cherry-picks, CI Failure Triage section with common
failure categories
- **logging.md**: Wave verification log template, CI failure report
table in session report

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10164-chore-add-CI-safety-rules-to-backport-management-skill-3266d73d365081aa856de1fb85a31887)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-25 12:17:43 -07:00
Alexander Brown
08ea013c51 fix: prune stale proxyWidgets referencing nodes removed by nested subgraph packing (#10390)
## Summary

Prune stale proxyWidgets entries that reference grandchild nodes no
longer present in the outer subgraph after nested packing.

## Changes

- **What**: Filter out proxyWidgets entries during hydration when the
source node doesn't exist in the subgraph. Also skip missing-node
entries in `_pruneStaleAliasFallbackEntries` as defense-in-depth. Write
back cleaned entries so stale data doesn't persist.

## Review Focus

The fix touches two codepaths in `SubgraphNode.ts`:
1. **Hydration** (`_internalConfigureAfterSlots`): Added `getNodeById`
guard before accepting a proxyWidget entry, and broadened the write-back
condition from legacy-only to any filtered entries.
2. **Runtime pruning** (`_pruneStaleAliasFallbackEntries`): Added
early-exit for entries whose source node no longer exists — previously
these survived because failed resolution returned `undefined` which
bypassed the concrete-key comparison.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10390-fix-prune-stale-proxyWidgets-referencing-nodes-removed-by-nested-subgraph-packing-32b6d73d365081e69eedcb2b67d7043d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-25 12:15:52 -07:00
Benjamin Lu
4aae52c2fc test: move getNodeDefs spec into src/scripts (#10503)
## Summary

Move the `getNodeDefs` unit test out of deprecated `tests-ui` and into
`src/scripts` so Vitest discovers and runs it.

## Changes

- **What**: Renamed `tests-ui/tests/scripts/app.getNodeDefs.test.ts` to
`src/scripts/app.getNodeDefs.test.ts`

## Review Focus

Confirm the spec now follows the colocated test convention and is
included by the existing Vitest `include` globs.

## Screenshots (if applicable)

N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10503-test-move-getNodeDefs-spec-into-src-scripts-32e6d73d3650816f9211dc4c20daba4b)
by [Unito](https://www.unito.io)
2026-03-25 12:15:18 -07:00
Benjamin Lu
6b7691422b fix: use named dotenv config imports (#10514)
## Summary

Use named `dotenv` config imports where we were calling
`dotenv.config()` so ESLint and IDEs stop flagging
`import-x/no-named-as-default-member`.

## Changes

- **What**: Replace default `dotenv` imports plus `.config()` member
access with `import { config as dotenvConfig } from 'dotenv'` in browser
test setup/fixture files and the desktop Vite config.
- **What**: Keep behavior unchanged while aligning those files with the
cleaner import form already used elsewhere in the repo.

## Review Focus

This is a no-behavior-change cleanup. The issue was that `dotenv`
exposes `config` both as a named export and as a property on the
default-exported module object, so `import dotenv from 'dotenv';
dotenv.config()` triggers `import-x/no-named-as-default-member` even
though it works at runtime.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10514-fix-use-named-dotenv-config-imports-32e6d73d36508195b346dbcab764a6b8)
by [Unito](https://www.unito.io)
2026-03-25 12:10:20 -07:00
Christian Byrne
437f41c553 perf: add layout/GC metrics + reduce false positives in regression detection (#10477)
## Summary

Add layout duration, style recalc duration, and heap usage metrics to CI
perf reports, while improving statistical reliability to reduce false
positive regressions.

## Changes

- **What**:
- Collect `layoutDurationMs`, `styleRecalcDurationMs`, `heapUsedBytes`
(absolute snapshot) alongside existing metrics
- Add effect size gate (`minAbsDelta`) for integer-quantized count
metrics (style recalcs, layouts, DOM nodes, event listeners) — prevents
z=7.2 false positives from e.g. 11→12 style recalcs
- Switch from mean to **median** for PR metric aggregation — robust to
outlier CI runs that dominate n=3 mean
- Increase historical baseline window from **5 to 15 runs** for more
stable σ estimates
- Reorder reported metrics: layout/style duration first (actionable),
counts and heap after (informational)

## Review Focus

The effect size gate in `classifyChange()` — it now requires both z > 2
AND absolute delta ≥ `minAbsDelta` (when configured) to flag a
regression. This addresses the core false positive issue where integer
metrics with near-zero historical variance produce extreme z-scores for
trivial changes.

Median vs mean tradeoff: median is more robust to outliers but less
sensitive to real shifts — acceptable given n=3 and CI noise levels.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10477-perf-add-layout-GC-metrics-reduce-false-positives-in-regression-detection-32d6d73d365081daa72cec96d8a07b90)
by [Unito](https://www.unito.io)
2026-03-25 10:16:56 -07:00
ComfyUI Wiki
975393b48b fix: restore is_template tracking for app mode templates (#10252)
## Summary

App mode templates (names ending in `.app`, e.g.
`templates-qwen_multiangle.app`) were never counted as template
executions in Mixpanel because `getExecutionContext` used
`activeWorkflow.filename` for the `knownTemplateNames` lookup — but
`getFilenameDetails` treats `.app.json` as a compound extension and
strips it entirely, leaving `"templates-qwen_multiangle"` instead of
`"templates-qwen_multiangle.app"`. The set lookup always returned
`false`, so every execution was sent with `is_template: false`.

## Changes

- **Fix**: derive the template lookup key from
`fullFilename.replace(/\.json$/i, '')` instead of `filename`, which
preserves the `.app` suffix and correctly matches `knownTemplateNames`
- **Also fixes**: `workflow_name`, `getTemplateByName`, and
`getEnglishMetadata` calls in the same branch now use the corrected name
- **Tests**: three new cases in `MixpanelTelemetryProvider.test.ts` —
regular template, `.app` template (regression), and non-template

## Before / After

| Template name in index | `activeWorkflow.filename` | `fullFilename` →
stripped | `is_template` |
|---|---|---|---|
| `flux-dev` | `flux-dev` | `flux-dev` |  true |
| `templates-qwen_multiangle.app` | `templates-qwen_multiangle`  |
`templates-qwen_multiangle.app`  | fixed: true |

## Review Focus

The change is confined to `getExecutionContext.ts`. `fullFilename` is
always set (it is assigned in `UserFile` constructor from
`getPathDetails`), so no null-safety issue.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10252-fix-restore-is_template-tracking-for-app-mode-templates-3276d73d365081d4b998edc62ad010dc)
by [Unito](https://www.unito.io)
2026-03-25 21:31:09 +08:00
Luke Mino-Altherr
a44fa1fdd5 fix: tighten date detection regex in formatJsonValue() (#10110)
`formatJsonValue()` uses a loose regex `^\d{4}-\d{2}-\d{2}` to detect
date-like strings, which matches non-date strings like
`"2024-01-01-beta"`.

Changes:
- Require ISO 8601 `T` separator: `/^\d{4}-\d{2}-\d{2}T/`
- Validate parse result with `!Number.isNaN(date.getTime())`
- Use `d()` i18n formatter for consistency with `formatDate()` in the
same file
2026-03-24 19:46:58 -07:00
Christian Byrne
cc3acebceb feat: scaffold Astro 5 website app + design-system base.css [1/3] (#10140)
## Summary
Scaffolds the new apps/website/ Astro 5 + Vue 3 marketing site inside
the monorepo.

## Changes
- apps/website/ with package.json, astro.config.mjs, tsconfig, Nx
targets
- @comfyorg/design-system/css/base.css — brand tokens + fonts (no
PrimeVue)
- pnpm-workspace.yaml catalog entries for Astro deps
- .gitignore and env.d.ts for Astro

## Stack (via Graphite)
- **[1/3] Scaffold** ← this PR
- #10141 [2/3] Layout Shell
- #10142 [3/3] Homepage Sections

Part of the comfy.org website refresh (replacing Framer).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10140-feat-scaffold-Astro-5-website-app-design-system-base-css-1-3-3266d73d365081688dcee0220a03eca4)
by [Unito](https://www.unito.io)
2026-03-24 19:02:10 -07:00
Alexander Brown
23c22e4c52 🧙 feat: wire ComfyHub publish wizard with profile gate, asset upload, and submission (#10128)
## Summary 🎯

Wire the ComfyHub publish flow end-to-end: profile gate, multi-step
wizard (describe, examples, finish), asset upload, and workflow
submission via hub API.

> *A wizard of steps, from describe to the end,*
> *Upload your assets, your workflows you'll send!*
> *With tags neatly slugged and thumbnails in place,*
> *Your ComfyHub publish is ready to race!* 🏁

## Changes 🔧

- 🌐 **Hub Service** — `comfyHubService` for profile CRUD, presigned
asset uploads, and workflow publish
- 📦 **Submission** — `useComfyHubPublishSubmission` orchestrates file
uploads → publish in one flow
- 🧙 **Wizard Steps** — Describe (name/description/tags) → Examples
(drag-drop reorderable images) → Thumbnail → Finish (profile card +
private-asset warnings)
- 🖼️ **ReorderableExampleImage** — Drag-drop *and* keyboard reordering,
accessible and fun
- 🏷️ **Tag Normalization** — `normalizeTags` slugifies before publishing
- 🔄 **Re-publish Prefill** — Fetches hub workflow metadata on
re-publish, with in-memory cache fallback
- 📐 **Schema Split** — Publish-record schema separated from
hub-workflow-metadata schema
- 🙈 **Unlisted Skip** — No hub-detail prefill fetch for unlisted records
- 👤 **Profile Gate** — Username validation in `useComfyHubProfileGate`
- 🧪 **Tests Galore** — New suites for DescribeStep, ExamplesStep,
WizardContent, PublishSubmission, comfyHubService, normalizeTags, plus
expanded PublishDialog & workflowShareService coverage

## Review Focus 🔍

> *Check the service, the schema, the Zod validation too,*
> *The upload orchestration — does it carry things through?*
> *The prefill fetch strategy: status → detail → cache,*
> *And drag-drop reordering — is it keeping its place?* 🤔

- 🌐 `comfyHubService.ts` — API contract shape, error handling, Zod
parsing
- 📦 `useComfyHubPublishSubmission.ts` — Upload-then-publish flow, edge
cases (no profile, no workflow)
- 🗂️ `ComfyHubPublishDialog.vue` — Prefill fetch strategy (publish
status → hub detail → cache)
- 🖼️ `ReorderableExampleImage.vue` — Drag-drop + keyboard a11y

## Testing 🧪

```bash
pnpm test:unit -- src/platform/workflow/sharing/
pnpm typecheck
```

> *If the tests all turn green and the types all align,*
> *Then merge it on in — this publish flow's fine!* 

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Dante <bunggl@naver.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-03-25 09:30:25 +09:00
Comfy Org PR Bot
88b54c6775 1.43.4 (#10411)
Patch version increment to 1.43.4

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10411-1-43-4-32d6d73d365081659c13eb6896c5ed96)
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>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
v1.43.4
2026-03-24 16:34:02 -07:00
Christian Byrne
e40995fb6c fix: restore Firebase getAdditionalUserInfo for sign-up telemetry OR logic (#10453)
## Summary

Restores `getAdditionalUserInfo` from Firebase Auth so sign-up telemetry
fires when *either* Firebase or the UI context identifies a new user,
fixing a regression from #10388.

## Changes

- **What**: In `loginWithGoogle` and `loginWithGithub`, call
`getAdditionalUserInfo(result)` and OR it with the UI-provided
`options?.isNewUser` flag: `is_new_user: options?.isNewUser ||
additionalUserInfo?.isNewUser || false`. Added 8 parameterized unit
tests covering the OR truth table (Firebase true, UI true, both false,
null result).

## Review Focus

The OR semantics: if either source says new user, we send `sign_up`
telemetry. Previously only the UI flag was checked, which missed cases
where the user lands directly on the OAuth provider without going
through the sign-up view.

## Testing

Unit tests cover all branches of the OR logic. An e2e test is not
feasible here because it would require completing a real OAuth flow with
Google/GitHub (interactive popup, valid credentials, CAPTCHA) and
intercepting the resulting `getAdditionalUserInfo` response from
Firebase — none of which can be reliably automated in a headless
Playwright environment without a live Firebase project seeded with
disposable accounts.

Fixes #10447
2026-03-24 12:39:48 -07:00
Christian Byrne
908a3ea418 fix: resolve subgraph node slot link misalignment during workflow load (#9121)
## Summary

Fix subgraph node slot connector links appearing misaligned after
workflow load, caused by a transform desync between LiteGraph's internal
canvas transform and the Vue TransformPane's CSS transform.

## Changes

- **What**: Changed `syncNodeSlotLayoutsFromDOM` to use DOM-relative
measurement (slot position relative to its parent `[data-node-id]`
element) instead of absolute canvas-space conversion via
`clientPosToCanvasPos`. This makes the slot offset calculation
independent of the global canvas transform, eliminating the frame-lag
desync that occurred when `fitView()` updated `lgCanvas.ds` before the
Vue CSS transform caught up.
- **Cleanup**: Removed the unreachable fallback path that still used
`clientPosToCanvasPos` when the parent node element wasn't found (every
slot element is necessarily a child of a `[data-node-id]` element — if
`closest()` fails the element is detached and measuring is meaningless).
This also removed the `conv` parameter from `syncNodeSlotLayoutsFromDOM`
and `flushScheduledSlotLayoutSync`, and the
`useSharedCanvasPositionConversion` import.
- **Test**: Added a Playwright browser test that loads a subgraph
workflow with `workflowRendererVersion: "LG"` (triggering the 1.2x scale
in `ensureCorrectLayoutScale`) as a template (triggering `fitView`), and
verifies all slot connector positions are within bounds of their parent
node element.

## Review Focus

- The core change is in `useSlotElementTracking.ts` — the new
measurement approach uses `getBoundingClientRect()` on both the slot and
its parent node element, then divides by `currentScale` to get
canvas-space offsets. This is simpler and more robust than the previous
approach.
- SubgraphNodes were disproportionately affected because they are
relatively static and don't often trigger `ResizeObserver`-based
re-syncs that would eventually correct stale offsets.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9121-fix-resolve-subgraph-node-slot-link-misalignment-during-workflow-load-3106d73d365081eca413c84f2e0571d6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <448862+DrJKL@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-24 12:28:12 -07:00
AustinMroz
0ae4b78cbc Use native context menu for focused textareas (#10454)
The custom context menu provided by the frontend exposes widget specific
options. In order to support renaming, promotion, and favoriting, there
needs to be a way to access this context menu when targeting a textarea.
However, always displaying this custom context menu will cause the user
to lose access to browser specific functionality like spell checking,
translation, and the ability to copy paste text.

This PR updates the behaviour so that the native browser context menu
will display when the text area already has focus. Our custom frontend
context menu will continue to display when it does not.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10454-Use-native-context-menu-for-focused-textareas-32d6d73d365081909673d81d6a6ba054)
by [Unito](https://www.unito.io)
2026-03-24 12:25:35 -07:00
Christian Byrne
5f14276159 [feat] Improve group title layout (#9839)
Rebased and adopted from #5774 by @felixturner.

## Changes
- Remove unused font-size properties (`NODE_TEXT_SIZE`,
`NODE_SUBTEXT_SIZE`, `DEFAULT_GROUP_FONT`) from theme palettes and color
palette schema
- Replace `DEFAULT_GROUP_FONT`/`DEFAULT_GROUP_FONT_SIZE` with a single
`GROUP_TEXT_SIZE = 20` constant (reduced from 24px)
- Use `NODE_TITLE_HEIGHT` for group header height instead of `font_size
* 1.4`
- Vertically center group title text using `textBaseline = 'middle'`
- Use `GROUP_TEXT_SIZE` directly in TitleEditor instead of per-group
`font_size`
- Remove `font_size` from group serialization (no longer per-group
configurable)

## Original PR
Closes #5774

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9839-feat-Improve-group-title-layout-3216d73d36508112a0edc2a370af20ba)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Felix Turner <felixturner@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-24 12:25:18 -07:00
Christian Byrne
df81c66725 fix: prevent blueprint cache corruption on repeated placement (#9897)
Add `structuredClone()` in `getBlueprint()` so `_deserializeItems()`
mutations (node IDs, subgraph UUIDs) don't corrupt cached data.

Includes regression test verifying cache immutability.

Fixes COM-16026

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9897-fix-prevent-blueprint-cache-corruption-on-repeated-placement-3226d73d3650815c8594fa8d266e680f)
by [Unito](https://www.unito.io)
2026-03-24 12:24:41 -07:00
Christian Byrne
c5ad6cf1aa fix: migrate V1 tab state pointers during V1→V2 draft migration (#10007)
## Problem

When upgrading from V1 to V2 draft persistence, users' open workflow
tabs were lost. V1 stored tab state (open paths + active index) in
localStorage via `setStorageValue` fallback, but the V1→V2 migration
only migrated draft payloads — not these tab state pointers.

This meant that after upgrading, all previously open tabs disappeared
and users had to manually reopen their workflows.

## Solution

Add `migrateV1TabState()` to the V1→V2 migration path. After draft
payloads are migrated, the function reads the V1 localStorage keys
(`Comfy.OpenWorkflowsPaths` and `Comfy.ActiveWorkflowIndex`) and writes
them to V2's sessionStorage format via `writeOpenPaths()`.

The `clientId` is threaded from `useWorkflowPersistenceV2` (which has
access to `api.clientId`) through to `migrateV1toV2()`.

## Changes

- **`migrateV1toV2.ts`**: Added `migrateV1TabState()` + V1 key constants
for tab state
- **`useWorkflowPersistenceV2.ts`**: Pass `api.clientId` to migration
call
- **`migrateV1toV2.test.ts`**: Two new tests proving tab state migration
works

## Testing

TDD approach — RED commit shows the test failing, GREEN commit shows it
passing.

All 123 persistence tests pass.

- Fixes #9974

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10007-fix-migrate-V1-tab-state-pointers-during-V1-V2-draft-migration-3256d73d36508103b619e521c1b603f5)
by [Unito](https://www.unito.io)
2026-03-24 12:23:59 -07:00
Terry Jia
fe1fae4de1 fix: disable preload error toast triggered by third-party plugin failures (#10445)
## Summary
The preload error toast fires whenever any custom node extension fails
to load via dynamic `import()`. In practice, this is almost always
caused by third-party plugin bugs rather than ComfyUI core issues.
Common triggers include:

- Bare module specifiers (e.g., `import from "vue"`) that the browser
cannot resolve without an import map
- Incorrect relative paths to `scripts/app.js` due to nested web
directory structures
  - Missing dependencies on other extensions (e.g., `clipspace.js`)

Since many users have multiple custom nodes installed, the toast
frequently appears on startup — sometimes multiple times — with a
generic message that offers no actionable guidance. This creates
unnecessary alarm and support burden.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10445-fix-disable-preload-error-toast-triggered-by-third-party-plugin-failures-32d6d73d365081f281efcd6fe90642a5)
by [Unito](https://www.unito.io)
2026-03-24 13:34:34 -04:00
Johnpaul Chiwetelu
38aad02fb9 test: add e2e tests for all default keybindings (#10440)
## Summary
- Add 21 Playwright E2E tests covering all previously untested default
keybindings
- Add `isReadOnly()` and `getOffset()` helpers to `CanvasHelper`

### Tests added
| Group | Keybindings | Count |
|-------|------------|-------|
| Sidebar toggles | `w`, `n`, `m`, `a` | 4 |
| Canvas controls | `Alt+=`, `Alt+-`, `.`, `h`, `v` | 5 |
| Node state | `Alt+c`, `Ctrl+m` | 2 |
| Mode/panel toggles | `Alt+m`, `Alt+Shift+m`, `` Ctrl+` `` | 3 |
| Queue/execution | `Ctrl+Enter`, `Ctrl+Shift+Enter`, `Ctrl+Alt+Enter` |
3 |
| File operations | `Ctrl+s`, `Ctrl+o` | 2 |
| Graph operations | `Ctrl+Shift+e`, `r` | 2 |

## Test plan
- [x] `pnpm exec playwright test defaultKeybindings.spec.ts` — 21/21
pass
- [x] `pnpm typecheck:browser` — clean
- [x] `pnpm lint` — clean

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10440-test-add-e2e-tests-for-all-default-keybindings-32d6d73d36508124a510d4742f9fbdbb)
by [Unito](https://www.unito.io)
2026-03-24 16:12:59 +01:00
pythongosssss
7c4234cf93 feat: App mode - add execution status messages (#10369)
## Summary

Adds custom status messages that are shown under the previews in order
to provide additional progress feedback to the user

Nodes matching the words:

Save, Preview -> Saving
Load, Loader -> Loading
Encode -> Encoding
Decode -> Decoding
Compile, Conditioning, Merge, -> Processing
Upscale, Resize -> Resizing
ToVideo -> Generating video

Specific nodes:
KSampler, KSamplerAdvanced, SamplerCustom, SamplerCustomAdvanced ->
Generating
Video Slice, GetVideoComponents, CreateVideo -> Processing video
TrainLoraNode -> Training


## Changes

- **What**: 
- add specific node lookups for non-easily matchable patterns
- add regex based matching for common patterns
- show on both latent preview & skeleton preview 
- allow app mode workflow authors to override status with custom
property `Execution Message` (no UI for doing this)

## Review Focus

This is purely pattern/lookup based, in future we could update the
backend node schema to allow nodes to define their own status key.

## Screenshots (if applicable)

<img width="757" height="461" alt="image"
src="https://github.com/user-attachments/assets/2b32cc54-c4e7-4aeb-912d-b39ac8428be7"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10369-feat-App-mode-add-execution-status-messages-32a6d73d3650814e8ca2da5eb33f3b65)
by [Unito](https://www.unito.io)

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-24 07:58:35 -07:00
jaeone94
4bfc730a0e fix: restore workflow tabs on browser restart (#10336)
## Problem

Since PR #8520 (`feat(persistence): fix QuotaExceededError and
cross-workspace draft leakage`), all workflow tabs are lost when the
browser is closed and reopened.

PR #8520 moved tab pointers (`ActivePath`, `OpenPaths`) from
`localStorage` to `sessionStorage` for per-tab isolation. However,
`sessionStorage` is cleared when the browser closes, so the open tab
list is lost on restart. The draft data itself survives in
`localStorage` — only the pointers to which tabs were open are lost.

Reported in
[Comfy-Org/ComfyUI#12984](https://github.com/Comfy-Org/ComfyUI/issues/12984).
Confirmed via binary search: v1.40.9 (last good) → v1.40.10 (first bad).

## Changes

Dual-write tab pointers to both storage layers:

- **sessionStorage** (scoped by `clientId`) — used for in-session
refresh, preserves per-tab isolation
- **localStorage** (scoped by `workspaceId`) — fallback for browser
restart when sessionStorage is empty

Also adds:
- `storageAvailable` guard on write functions for consistency with
`writeIndex`/`writePayload`
- `isValidPointer` validation on localStorage reads to reject stale or
malformed data

## Benefits

- Workflow tabs survive browser restart (restores V1 behavior)
- Per-tab isolation is preserved for in-session use (sessionStorage is
still preferred when available)

## Trade-offs

- On browser restart, the restored tabs come from whichever browser tab
wrote last to localStorage. If Tab A had workflows 1,2,3 and Tab B had
4,5 — the user gets whichever tab wrote most recently. This is the same
limitation V1 had with `Comfy.OpenWorkflowsPaths` in localStorage.
- Previously (post-#8520), opening a new browser tab would only restore
the single most recent draft. With this fix, a new tab restores the full
set of open tabs from the last session. This may be surprising for
multi-tab users who expect a clean slate in new tabs.

## Test plan

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] All 121 persistence tests pass
- [x] Manual: open multiple workflow tabs → close browser → reopen →
tabs restored
- [x] Manual: open two browser tabs with different workflows → refresh
each → correct tabs in each

Fixes Comfy-Org/ComfyUI#12984

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10336-fix-restore-workflow-tabs-on-browser-restart-3296d73d365081b7a7d3e91427d08d17)
by [Unito](https://www.unito.io)
<!-- QA_REPORT_SECTION -->
---
## 🔍 Automated QA Report

| | |
|---|---|
| **Status** |  Complete |
| **Report** |
[sno-qa-10336.comfy-qa.pages.dev](https://sno-qa-10336.comfy-qa.pages.dev/)
|
| **CI Run** | [View
workflow](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23373697656)
|

Before/after video recordings with **Behavior Changes** and **Timeline
Comparison** tables.
2026-03-24 17:30:36 +09:00
Yourz
001916edf6 refactor: clean up essentials node organization logic (#10433)
## Summary

Refactor essentials tab node organization to eliminate duplicated logic
and restrict essentials to core nodes only.

## Changes

- **What**: 
- Extract `resolveEssentialsCategory` to centralize category resolution
(was duplicated between filter and pathExtractor).
- Add `isCoreNode` guard so third-party nodes never appear in
essentials.
- Replace `indexOf`-based sorting with precomputed rank maps
(`ESSENTIALS_CATEGORY_RANK`, `ESSENTIALS_NODE_RANK`).

<img width="589" height="769" alt="image"
src="https://github.com/user-attachments/assets/66f41f35-aef5-4e12-97d5-0f33baf0ac45"
/>


## Review Focus

- The `isCoreNode` guard in `resolveEssentialsCategory` — ensures only
core nodes can appear in essentials even if a custom node sets
`essentials_category`.
- Rank map precomputation vs previous `indexOf` — functionally
equivalent but O(1) lookup.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10433-refactor-clean-up-essentials-node-organization-logic-32d6d73d36508193a4d1f7f9c18fcef7)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-24 16:22:09 +08:00
jaeone94
66daa6d645 refactor: error system cleanup — store separation, DDD fix, test improvements (#10302)
## Summary

Refactors the error system to improve separation of concerns, fix DDD
layer violations, and address code quality issues.

- Extract `missingNodesErrorStore` from `executionErrorStore`, removing
the delegation pattern that coupled missing-node logic into the
execution error store
- Extract `useNodeErrorFlagSync` composable for node error flag
reconciliation (previously inlined)
- Extract `useErrorClearingHooks` composable with explicit callback
cleanup on node removal
- Extract `useErrorActions` composable to deduplicate telemetry+command
patterns across error card components
- Move `getCnrIdFromNode`/`getCnrIdFromProperties` to
`platform/nodeReplacement` layer (DDD fix)
- Move `missingNodesErrorStore` to `platform/nodeReplacement` (DDD
alignment)
- Add unmount cancellation guard to `useErrorReport` async `onMounted`
- Return watch stop handle from `useNodeErrorFlagSync`
- Add `asyncResolvedIds` eviction on `missingNodesError` reset
- Add `console.warn` to silent catch blocks and empty array guard
- Hoist `useCommandStore` to setup scope, fix floating promises
- Add `data-testid` to error groups, image/video error spans, copy
button
- Update E2E tests to use scoped locators and testids
- Add unit tests for `onNodeRemoved` restoration and double-install
guard

Fixes #9875, Fixes #10027, Fixes #10033, Fixes #10085

## Test plan

- [x] Existing unit tests pass with updated imports and mocks
- [x] New unit tests for `useErrorClearingHooks` (callback restoration,
double-install guard)
- [x] E2E tests updated to use scoped locators and `data-testid`
- [ ] Manual: verify error tab shows runtime errors and missing nodes
correctly
- [ ] Manual: verify "Find on GitHub", "Copy", and "Get Help" buttons
work in error cards

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10302-refactor-error-system-cleanup-store-separation-DDD-fix-test-improvements-3286d73d365081838279d045b8dd957a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-24 16:43:22 +09:00
jaeone94
32a53eeaee fix: sync advanced inputs button color with node header (#10427)
## Summary
- The "Show Advanced Inputs" footer button was missing `headerColor`
style binding, causing it to not sync with the node header color (unlike
the "Enter Subgraph" button which already had it)
- Extracted the repeated `{ backgroundColor: headerColor }` inline style
(4 occurrences) into a `headerColorStyle` computed

## Screenshots 
before 
<img width="211" height="286" alt="스크린샷 2026-03-24 154312"
src="https://github.com/user-attachments/assets/edfd9480-04fa-4cd4-813d-a95adffbe2d3"
/>

after 
<img width="261" height="333" alt="스크린샷 2026-03-24 154622"
src="https://github.com/user-attachments/assets/eab28717-889e-4a6b-8775-bfc08fa727ff"
/>

## Test plan
- [x] Set a custom color on a node with advanced inputs and verify the
footer button matches the header color
- [x] Verify subgraph enter button still syncs correctly
- [x] Verify dual-tab layouts (error + advanced, error + subgraph) both
show correct colors

### Why no E2E test
Node header color is applied as an inline style via `headerColor` prop,
which is already passed and tested through the existing subgraph enter
button path. This change simply extends the same binding to the advanced
inputs buttons — no new data flow or interaction is introduced, so a
screenshot-based E2E test would add maintenance cost without meaningful
regression coverage.
2026-03-24 16:00:55 +09:00
Jin Yi
7c6ab19484 fix: manager progress toast and install button UX issues (#10423)
## Changes

- Reset `isRestartCompleted` in `closeToast()` so the "Apply Changes"
button appears correctly on subsequent installs instead of skipping to
the success message
- Add `@click.stop` on `PackInstallButton` to prevent click from
bubbling up to card selection, which was unintentionally opening the
right info panel

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10423-fix-manager-progress-toast-and-install-button-UX-issues-32d6d73d365081eba2c6d5b9b35ab26e)
by [Unito](https://www.unito.io)
2026-03-23 21:57:16 -07:00
Dante
8f9fe3b21e refactor: rebuild SingleSelect and MultiSelect with Reka UI (#9742)
## Summary
- Rebuild `SingleSelect` with Reka UI Select primitives (SelectRoot,
SelectTrigger, SelectContent, SelectItem)
- Rebuild `MultiSelect` with Reka UI Popover + Listbox (PopoverRoot,
ListboxRoot with `multiple`)
- Remove PrimeVue dependency from both components
- Preserve existing API contract for all consumers

## TODO
- [x] Re-evaluate MultiSelect implementation (ComboboxRoot with
`multiple` may be cleaner than Popover+Listbox)
- [x] E2E verification in actual app (AssetFilterBar,
WorkflowTemplateSelectorDialog, etc.)

## Test plan
- [x] Storybook visual verification for all SingleSelect/MultiSelect
stories
- [x] Keyboard navigation (arrow keys, Enter, Escape, typeahead)
- [x] Multi-selection with badge count
- [x] Search filtering in MultiSelect
- [x] Clear all functionality
- [x] Disabled state
- [x] Invalid state (SingleSelect)
- [x] Loading state (SingleSelect)

## screenshot
<img width="519" height="475" alt="스크린샷 2026-03-20 오전 12 12 37"
src="https://github.com/user-attachments/assets/ffc7f0b0-c88c-486b-a253-73a4da73c1de"
/>
<img width="842" height="554" alt="스크린샷 2026-03-20 오전 12 23 51"
src="https://github.com/user-attachments/assets/410551d4-c843-4898-b305-13a6ad6978ca"
/>

## video


https://github.com/user-attachments/assets/2fc3a9b9-2671-4c2c-9f54-4f83598afb53



Fixes #9700

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9742-refactor-rebuild-SingleSelect-and-MultiSelect-with-Reka-UI-3206d73d36508113bee2cf160c8f2d50)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-24 04:39:20 +00:00
Dante
4411d9a417 refactor: extract shared click-vs-drag guard utility (#10357)
## Summary

Extract duplicated click-vs-drag detection logic into a shared
`useClickDragGuard` composable and `exceedsClickThreshold` pure utility
function.

## Changes

- **What**: New `useClickDragGuard(threshold)` composable in
`src/composables/useClickDragGuard.ts` that stores pointer start
position and checks squared distance against a threshold. Also exports
`exceedsClickThreshold` for non-Vue contexts.
- Migrated `DropZone.vue`, `useNodePointerInteractions.ts`, and
`Load3d.ts` to use the shared utility
- `CanvasPointer.ts` left as-is (LiteGraph internal)
- All consumers now use squared-distance comparison (no `Math.sqrt` or
per-axis `Math.abs`)

## Review Focus

- The composable uses plain `let` state instead of `ref` since
reactivity is not needed for the start position
- `Load3d.ts` uses the pure `exceedsClickThreshold` function directly
since it is a class, not a Vue component
- Threshold values preserved per-consumer: DropZone=5,
useNodePointerInteractions=3, Load3d=5

Fixes #10356

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10357-refactor-extract-shared-click-vs-drag-guard-utility-32a6d73d3650816e83f5cb89872fb184)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-24 12:27:42 +09:00
Jin Yi
98d56bdada fix: improve settings dialog UX (#10396)
## Summary

Reduce settings dialog size and autofocus search input for better
usability.

## Changes

- **What**: Reduce dialog size from `md` to `sm` (max-width 1400px →
960px); autofocus search input on open

## Review Focus

User feedback indicated the settings dialog was too wide and search
required an extra click to focus.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10396-fix-improve-settings-dialog-UX-32c6d73d365081e29eceed55afde1967)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-23 18:45:40 -07:00
Christian Byrne
b4bb6d9d13 feat: add grid view mode for multi-image batches in ImagePreview (#9241)
## Summary

Add grid view mode for multi-image batches in ImagePreview (Nodes 2.0),
replicating the Nodes 1.0 grid UX where all output images are visible as
clickable thumbnails.

## Changes

- **What**: Multi-image batches now default to a grid view showing all
thumbnails. Clicking a thumbnail switches to gallery mode for that
image. A persistent back-to-grid button sits next to navigation dots,
and hover action bars provide gallery toggle, download, and remove.
Replaced PrimeVue `Skeleton` with shadcn `Skeleton`. Added `viewGrid`,
`viewGallery`, `imageCount`, `galleryThumbnail` i18n keys.

## Review Focus

- Grid column count strategy: fixed breakpoints (2 cols ≤4, 3 cols ≤9, 4
cols 10+) vs CSS auto-fill
- Default view mode: grid for multi-image, gallery for single — matches
Nodes 1.0 behavior
- `object-contain` on thumbnails to avoid cropping (with `aspect-square`
containers for uniform cells)

Fixes #9162

<!-- Pipeline-Ticket: f8f8effa-adff-4ede-b1d3-3c4f04b9c4a0 -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9241-feat-add-grid-view-mode-for-multi-image-batches-in-ImagePreview-3136d73d36508166895ed6c635150434)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Alexander Brown <448862+DrJKL@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-03-24 09:49:44 +09:00
Matt Miller
6a9fb4e1d5 fix: show clear error dialog for 403 whitelist failures (#10402)
## Summary
- When the cloud backend returns a 403 (user not whitelisted), the
frontend showed a generic "Prompt Execution Error" dialog with a cryptic
message
- Now catches 403 responses specifically and shows an "Access
Restricted" dialog with the backend's actual error message
- Adds `status` field to `PromptExecutionError` so error handlers can
distinguish HTTP status codes

## Changes
- `api.ts`: Added optional `status` to `PromptExecutionError`, pass
`res.status` from `queuePrompt`
- `app.ts`: New `else if` branch in the prompt error handler for `status
=== 403` — shows "Access Restricted" with the backend message

## Backwards compatible
- **Old backend** (`"not authorized"`): Shows "Access Restricted: not
authorized"
- **New backend**
([cloud#2941](https://github.com/Comfy-Org/cloud/pull/2941), `"your
account is not whitelisted for this feature"`): Shows "Access
Restricted: your account is not whitelisted for this feature"
- No behavior change for non-403 errors

## Related
- Backend fix: Comfy-Org/cloud#2941
- Notion: COM-16179

## Test plan
- [ ] Submit a prompt as a non-whitelisted user → should see "Access
Restricted" dialog with clear message
- [ ] Submit a prompt as a whitelisted user → no change in behavior
- [ ] Submit a prompt that fails for other reasons (missing nodes, etc.)
→ existing error handling unchanged

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10402-fix-show-clear-error-dialog-for-403-whitelist-failures-32c6d73d365081eb9528d7feac4e8681)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Matt Miller <mattmiller@Matts-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:57:12 +00:00
Deep Mehta
2838edbb53 feat: send missing node data to ClickHouse (#10132)
## Summary
- When users open/import a workflow with missing nodes, we already track
this via Mixpanel
- This adds a parallel fire-and-forget POST to
`/api/internal/cloud_analytics` so the data also lands in **ClickHouse**
as `frontend:missing_nodes_detected` events
- Payload includes `missing_class_types[]`, `missing_count`, and
`source` (file_button/file_drop/template/unknown)

## Motivation
The frontend is where the **high-value** missing node signal lives —
most users see "missing nodes" and never submit. The backend only
catches the rare case where someone submits anyway. This change captures
both sides.

Companion cloud PR: https://github.com/Comfy-Org/cloud/pull/2886

## Changes
- `MixpanelTelemetryProvider.ts`: Added
`reportMissingNodesToClickHouse()` private method, called from
`trackWorkflowImported()` and `trackWorkflowOpened()`
- Only fires when `missing_node_count > 0`
- Fire-and-forget (`.catch(() => {})`) — no impact on user experience
- Uses existing `api.fetchApi()` which handles auth automatically

## Test plan
- [ ] Open a workflow with missing nodes → verify
`frontend:missing_nodes_detected` event appears in ClickHouse
- [ ] Open a workflow with no missing nodes → verify no event is sent
(check network tab)
- [ ] Verify Mixpanel tracking still works as before

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10132-feat-send-missing-node-data-to-ClickHouse-3266d73d365081559db5ed3efde33e95)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-23 15:39:04 -07:00
Arthur R Longbottom
657ae6a6c3 fix: subgraph promoted widget input label rename (#10195)
## Summary

Promoted primitive subgraph inputs (String, Int) render their link
anchor at the header position instead of the widget row. Renaming
subgraph input labels breaks the match entirely, causing connections to
detach from their widgets visually.

## Changes

- **What**: Fix widget-input slot positioning for promoted subgraph
inputs in both LiteGraph and Vue (Nodes 2.0) rendering modes
- `_arrangeWidgetInputSlots`: Removed Vue mode branch that skipped
setting `input.pos`. Promoted widget inputs aren't rendered as
`<InputSlot>` Vue components (NodeSlots filters them out), so
`input.pos` is the only position fallback
- `drawConnections`: Added pre-pass to arrange nodes with unpositioned
widget-input slots before link rendering. The background canvas renders
before the foreground canvas calls `arrange()`, so positions weren't set
on the first frame
- `SubgraphNode`: Sync `input.widget.name` with the display name on
label rename and initial setup. The `IWidgetLocator` name diverged from
`PromotedWidgetView.name` after rename, breaking all name-based
slot↔widget matching (`_arrangeWidgetInputSlots`, `getWidgetFromSlot`,
`getSlotFromWidget`)

## Review Focus

- The `_arrangeWidgetInputSlots` rewrite iterates `_concreteInputs`
directly instead of building a spread-copy map — simpler and avoids the
stale index issue
- `input.widget.name` is now kept in sync with the display name
(`input.label ?? subgraphInput.name`). This is a semantic shift from
using the raw internal name, but it's required for all name-based
matching to work after renames. The value is overwritten on deserialize
by `_setWidget` anyway
- The `_widget` fallback in `_arrangeWidgetInputSlots` is a safety net
for edge cases where the name still doesn't match (e.g., stale cache)

Fixes #9998

## Screenshots
<img width="847" height="476" alt="Screenshot 2026-03-17 at 3 05 32 PM"
src="https://github.com/user-attachments/assets/38f10563-f0bc-44dd-a1a5-f4a7832575d0"
/>
<img width="804" height="471" alt="Screenshot 2026-03-17 at 3 05 23 PM"
src="https://github.com/user-attachments/assets/3237a7ee-f3e5-4084-b330-371def3415bd"
/>
<img width="974" height="571" alt="Screenshot 2026-03-17 at 3 05 16 PM"
src="https://github.com/user-attachments/assets/cafdca46-8d9b-40e1-8561-02cbb25ee8f2"
/>
<img width="967" height="558" alt="Screenshot 2026-03-17 at 3 05 06 PM"
src="https://github.com/user-attachments/assets/fc03ce43-906c-474d-b3bc-ddf08eb37c75"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10195-fix-subgraph-promoted-widget-input-slot-positions-after-label-rename-3266d73d365081dfa623dd94dd87c718)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jaeone94 <jaeone.prt@gmail.com>
2026-03-23 14:33:03 -07:00
Comfy Org PR Bot
f37b0daa3a 1.43.3 (#10225)
Patch version increment to 1.43.3

**Base branch:** `main`

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
v1.43.3
2026-03-23 14:24:37 -07:00
Terry Jia
debbdfd1f7 test: verify gradient_stops survives object spread (#10408)
## Summary
follow up https://github.com/Comfy-Org/ComfyUI_frontend/pull/10406 to
add test

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10408-test-verify-gradient_stops-survives-object-spread-32c6d73d3650815da123c88f4b9b86ce)
by [Unito](https://www.unito.io)
2026-03-23 13:39:49 -07:00
Deep Mehta
9a5ee462f1 refactor: extract model-to-node mappings into separate data file (#10237)
## Summary
- Extract all 75 `quickRegister()` mapping entries from
`modelToNodeStore.ts` into a new `modelNodeMappings.ts` constants file
- The store now iterates over the `MODEL_NODE_MAPPINGS` array instead of
having inline calls
- **Zero behavioral change** — same mappings, same order, same runtime
behavior

## Motivation
Adding new model-to-node mappings is currently a code change to the
store. By separating the data into its own file:
- New mappings are a **pure data change** (append a tuple to an array)
- The data file can have its own CODEOWNERS entry, so mapping PRs can be
merged without requiring frontend team review
- Easier to audit — all mappings visible in one place without
interleaved store logic

### Before
```ts
// 250+ lines of quickRegister() calls mixed into store logic
quickRegister('checkpoints', 'CheckpointLoaderSimple', 'ckpt_name')
quickRegister('checkpoints', 'ImageOnlyCheckpointLoader', 'ckpt_name')
// ... 73 more
```

### After
```ts
// modelNodeMappings.ts — pure data
export const MODEL_NODE_MAPPINGS = [
  ['checkpoints', 'CheckpointLoaderSimple', 'ckpt_name'],
  ['checkpoints', 'ImageOnlyCheckpointLoader', 'ckpt_name'],
  // ...
]

// modelToNodeStore.ts — just iterates
for (const [modelType, nodeClass, key] of MODEL_NODE_MAPPINGS) {
  quickRegister(modelType, nodeClass, key)
}
```

## Test plan
- [ ] "Use" button in model browser still works for all model types
- [ ] No regressions in model-to-node resolution (same mappings, same
order)

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10237-refactor-extract-model-to-node-mappings-into-separate-data-file-3276d73d365081988656e2ddae772bbc)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-03-23 13:26:42 -07:00
Johnpaul Chiwetelu
cff6a2255c fix: skip scheduled workflow runs on forks (#10407)
Adds `if: github.repository == 'Comfy-Org/ComfyUI_frontend'` to the
`Weekly Documentation Check` and `Release: Version Bump` workflows so
their scheduled runs are skipped on forks (where they fail due to
missing secrets).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10407-fix-skip-scheduled-workflow-runs-on-forks-32c6d73d365081d1b847d9f26b20efeb)
by [Unito](https://www.unito.io)
2026-03-23 20:00:47 +00:00
AustinMroz
860d049487 Fix snap offset for reroutes and subgraph IO (#10229)
When snapping to grid, reroutes and subgraph IO would snap at an awful y
offset.

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/285cfd52-2242-4242-b031-926677575542"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/4920ef9b-8eb1-4330-8eea-9992ad662018"
/>|


Regretfully, the PR leaves more magic constants than I would like.
There's a hardcoded `0.7` multiplier on `NODE_SLOT_HEIGHT` that isn't
defined as a constant, and it seems circular imports prevent constants
being used to declare the subgraphIO `roundedRadius`

See #8838 (Sorry for the delay. This change wound up being real simple)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10229-Fix-snap-offset-for-reroutes-and-subgraph-IO-3276d73d365081c2866dc388898a1be4)
by [Unito](https://www.unito.io)
2026-03-23 12:59:47 -07:00
Terry Jia
2d88720d80 fix: make gradient_stops enumerable so it survives object spread (#10406)
## Summary
Object.defineProperty defaults to enumerable: false for new properties.
NodeWidgets.vue spreads widget options into a new object, dropping
non-enumerable properties — gradient_stops was silently lost, causing
the gradient slider to fall back to the default black-to-white gradient.

## Screenshots (if applicable)

before
<img width="1679" height="1271" alt="image"
src="https://github.com/user-attachments/assets/60a20163-56e5-4438-9c36-ea42c23c7495"
/>

after
<img width="1686" height="1421" alt="image"
src="https://github.com/user-attachments/assets/5057b3d4-32ee-4abc-9118-cc2c3f85ae85"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10406-fix-make-gradient_stops-enumerable-so-it-survives-object-spread-32c6d73d36508131b7c8c3ab3e93d221)
by [Unito](https://www.unito.io)
2026-03-23 15:55:47 -04:00
Christian Byrne
818db0d49e fix: use UI flow context for sign_up vs login telemetry (#10388)
## Problem

GA4 shows **zero** `sign_up` events from cloud.comfy.org. All new users
(Google/GitHub) are tracked as `login` instead of `sign_up`.

**Root cause:** `getAdditionalUserInfo(result)?.isNewUser` from Firebase
is unreliable for popup auth flows — it compares `creationDate` vs
`lastSignInDate` timestamps, which can differ even for genuinely new
users. When it returns `null` or `false`, the code defaults to pushing a
`login` event instead of `sign_up`.

**Evidence:** GA4 Exploration filtered to `sign_up` + `cloud.comfy.org`
shows 0 events, while `login` shows 8,804 Google users, 519 email, 193
GitHub — all new users are being misclassified as logins.

(Additionally, the ~300 `sign_up` events visible in GA4 are actually
from `blog.comfy.org` — Substack newsletter subscriptions — not from the
app at all.)

## Fix

Use the UI flow context to determine `is_new_user` instead of Firebase's
unreliable API:
- `CloudSignupView.vue` → passes `{ isNewUser: true }` (user is on the
sign-up page)
- `CloudLoginView.vue` → no flag needed, defaults to `false` (user is on
the login page)
- `SignInContent.vue` → passes `{ isNewUser: !isSignIn.value }` (dialog
toggles between sign-in/sign-up)
- Removes the unused `getAdditionalUserInfo` import

## Changes

- `firebaseAuthStore.ts`: `loginWithGoogle`/`loginWithGithub` accept
optional `{ isNewUser }` parameter instead of calling
`getAdditionalUserInfo`
- `useFirebaseAuthActions.ts`: passes the option through
- `CloudSignupView.vue`: passes `{ isNewUser: true }`
- `SignInContent.vue`: passes `{ isNewUser: !isSignIn.value }`

## Testing

- All 32 `firebaseAuthStore.test.ts` tests pass
- All 19 `GtmTelemetryProvider.test.ts` tests pass
- Typecheck passes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10388-fix-use-UI-flow-context-for-sign_up-vs-login-telemetry-32b6d73d3650811e96cec281108abbf3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-23 10:11:40 -07:00
Hunter
cd45efa983 feat: differentiate personal/team pricing table with two-stage team workspace flow (#9901)
## Summary

Differentiates the subscription pricing dialog between personal and team
workspaces with distinct visual treatments and a two-stage team
workspace upgrade flow.

### Changes

- **Personal pricing dialog**: Shows "P" avatar badge, "Plans for
Personal Workspace" header, and "Solo use only – Need team workspace?"
banner on each tier card
- **Team pricing dialog**: Shows workspace avatar, "Plans for Team
Workspace" header (emerald), green "Invite up to X members" badge, and
emerald border on Creator card
- **Two-stage upgrade flow**: "Need team workspace?" → closes pricing →
opens CreateWorkspaceDialog → sessionStorage flag → page reload →
WorkspaceAuthGate auto-opens team pricing dialog
- **Spacing**: Reduced vertical gaps/padding/font sizes so the table
fits without scrolling

### Key decisions

- sessionStorage key `comfy:resume-team-pricing` bridges the page reload
during workspace creation
- `onChooseTeam` prop is conditionally passed only to the personal
variant
- `resumePendingPricingFlow()` is called from WorkspaceAuthGate after
workspace initialization

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9901-feat-differentiate-personal-team-pricing-table-with-two-stage-team-workspace-flow-3226d73d365081e7af60dcca86e83673)
by [Unito](https://www.unito.io)
2026-03-23 09:17:19 -07:00
AustinMroz
bd322314bc Display a separate indicator for pending jobs in app mode (#10382)
Previously, a pending, but not yet running job was displayed as an empty
skeleton. It is instead updated to display an animated `lucide--loader`
icon.

<img width="1544" height="1347" alt="image"
src="https://github.com/user-attachments/assets/4f82185c-97dc-44af-8ea1-a012fb992fe2"
/>

As this shifts the activeQueueItem component to display even when only
the single pending item is in the queue, the count badge is also update
to not display when this is the case.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10382-Display-a-separate-indicator-for-pending-jobs-in-app-mode-32a6d73d36508189b3a9ff4b84993a98)
by [Unito](https://www.unito.io)
2026-03-23 09:09:09 -07:00
Dante
b33c8c2d30 revert: fix: prevent survey popup when surveyed feature is inactive (#10360) (#10393)
## Summary

Reverts #10360

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10393-revert-fix-prevent-survey-popup-when-surveyed-feature-is-inactive-10360-32b6d73d36508132aff9c0e21529efac)
by [Unito](https://www.unito.io)
2026-03-22 14:40:21 +09:00
Simon Pinfold
aa407e7cd4 fix: show all outputs in FormDropdown for multi-output jobs (#10131)
## Summary

FormDropdown Outputs tab only showed the first output for multi-output
jobs because the Jobs API `/jobs` returns a single `preview_output` per
job.

## Changes

- **What**: When history assets include jobs with `outputs_count > 1`,
lazily fetch full outputs via `getJobDetail` (cached in
`jobOutputCache`) and expand them into individual dropdown items.
Single-output jobs are unaffected. Added in-flight guard to prevent
duplicate fetches.
- This is a consumer-side workaround in `WidgetSelectDropdown.vue` that
becomes a no-op once the backend returns all outputs in the list
response (planned Assets API migration).

## Review Focus

- The `resolvedMultiOutputs` shallowRef + watch pattern for async data
feeding into a computed. Each `getJobDetail` call is cached by
`jobOutputCache` LRU, so no redundant network requests.
- This fix is intentionally temporary — it will be superseded when
OSS/cloud both return full outputs from list endpoints.

## No E2E test

E2E coverage is impractical here: reproducing requires a running ComfyUI
backend executing a workflow that produces multiple outputs, then
inspecting the FormDropdown's Outputs tab. The unit test covers the
lazy-loading logic with mocked `getJobDetail` responses.

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-21 16:40:30 -07:00
Dante
a9dce7aa20 fix: prevent survey popup when surveyed feature is inactive (#10360)
## Summary

Prevent the nightly survey popup from appearing when the feature being
surveyed is currently disabled.

## Root Cause

`useSurveyEligibility` checks the feature usage count from localStorage
but does not verify whether the surveyed feature is currently active.
When a user uses the new node search 3+ times (reaching the survey
threshold), then switches back to legacy search, the usage count
persists in localStorage and the survey popup still appears despite the
feature being off.

## Steps to Reproduce

1. Enable new node search (Settings > Node Search Box Implementation >
"default")
2. Use node search 3+ times (double-click canvas, search for nodes)
3. Switch back to legacy search (Settings > Node Search Box
Implementation > "litegraph (legacy)")
4. Wait 5 seconds on a nightly localhost build
5. **Expected**: No survey popup appears (feature is disabled)
6. **Actual**: Survey popup appears because eligibility only checks
stored usage count, not current feature state

## Changes

1. Added optional `isFeatureActive` callback to `FeatureSurveyConfig`
interface
2. Added `isFeatureActive` guard in `useSurveyEligibility` eligibility
computation
3. Configured `node-search` survey with `isFeatureActive` that checks
the current search box setting

## Red-Green Verification

| Commit | Result | Description |
|---|---|---|
| `99e7d7fe9` `test:` | RED | Asserts `isEligible` is false when
`isFeatureActive` returns false. Fails on current code. |
| `01df9af12` `fix:` | GREEN | Adds `isFeatureActive` check to
eligibility. Test passes. |

Fixes #10333
2026-03-22 07:38:41 +09:00
Alexander Brown
20738b6349 test: add Vue Testing Library infrastructure and pilot migration (#10319)
## Summary

Add Vue Testing Library (VTL) infrastructure and pilot-migrate
ComfyQueueButton.test.ts as Phase 0 of an incremental VTL adoption.

## Changes

- **What**: Install `@testing-library/vue`,
`@testing-library/user-event`, `@testing-library/jest-dom`, and
`eslint-plugin-testing-library`. Configure jest-dom matchers globally
via `vitest.setup.ts` and `tsconfig.json`. Create shared render wrapper
at `src/utils/test-utils.ts` (pre-configures PrimeVue, Pinia, i18n).
Migrate `ComfyQueueButton.test.ts` from `@vue/test-utils` to VTL. Add
warn-level `testing-library/*` ESLint rules for test files.
- **Dependencies**: `@testing-library/vue`,
`@testing-library/user-event`, `@testing-library/jest-dom`,
`eslint-plugin-testing-library`

## Review Focus

- `src/utils/test-utils.ts` — shared render wrapper typing approach
(uses `ComponentMountingOptions` from VTU since VTL's `RenderOptions`
requires a generic parameter)
- ESLint rules are all set to `warn` during migration to avoid breaking
existing VTU tests
- VTL coexists with VTU — no existing tests are broken

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10319-test-add-Vue-Testing-Library-infrastructure-and-pilot-migration-3286d73d3650812793ccd8a839550a04)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-21 13:40:15 -07:00
Alexander Brown
477f9c7631 chore: upgrade nx 22.5.2 → 22.6.1 (#10370)
## Summary

Upgrade Nx from 22.5.2 to 22.6.1.

## Changes

- **What**: Bumped nx, @nx/eslint, @nx/playwright, @nx/storybook, and
@nx/vite from 22.5.2 to 22.6.1.
- **Dependencies**: nx, @nx/eslint, @nx/playwright, @nx/storybook,
@nx/vite updated to 22.6.1.

## Review Focus

All Nx migrations ran with no changes needed — this is a straightforward
version bump.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10370-chore-upgrade-nx-22-5-2-22-6-1-32a6d73d36508191988bdc7376dc5e14)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-21 17:41:36 +00:00
pythongosssss
dee494f019 feat: App mode - Switch to Nodes 2.0 when entering builder (#10337)
## Summary

We've had some reports of issues selecting inputs/nodes when trying to
use the builder in LiteGraph mode and due to the complexity of the
canvas system, we're going to enable Nodes 2.0 when entering the builder
to ensure the best experience.

## Changes

- **What**:  
- When entering builder select mode automatically switch to Nodes 2.0
- Extract reusable component from features toast
- Show popup telling user the mode was changed
- Add hidden setting for storing "don't show again" on the switch popup

## Review Focus
- I have not removed the LiteGraph selection code in case someone still
manages to enter the builder in LiteGraph mode, this should be cleaned
up in future

## Screenshots (if applicable)

<img width="423" height="224" alt="image"
src="https://github.com/user-attachments/assets/cc2591bc-e5dc-47ef-a3c6-91ca7b6066ff"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10337-feat-App-mode-Switch-to-Nodes-2-0-when-entering-builder-3296d73d3650818e9f3cdaac59d15609)
by [Unito](https://www.unito.io)
2026-03-21 10:38:59 -07:00