Commit Graph

8065 Commits

Author SHA1 Message Date
DrJKL
b8b6f51b3a refactor(subgraph): address review nits on promotion + migration
- proxyWidgetMigration: switch repairValue/migratePreview to exhaustive
  switches with a shared `assertUnreachablePlan(plan: never)` helper so
  adding a new Plan kind triggers a TS error at the dispatcher.
- previewExposureStore: drop unused `moveExposure` action (and its
  tests); document deferred Object.freeze hardening at getExposures.
- usePromotedPreviews: drop optional chaining on
  nodeOutputStore.getNode*ByExecutionId methods (always defined).
- BaseWidget: drop the unused `_suppressPromotedOutline` parameter on
  `getOutlineColor` and the `suppressPromotedOutline` field on
  `DrawWidgetOptions`; remove all 14 call sites.
- SubgraphNodeWidget.vue: switch to tuple-form `defineEmits<{ ... }>()`.
- LGraph: rewrite the `proxyWidgetMigrationFlush` JSDoc to cite the
  real reason for late binding (the migration module's transitive
  workbench imports, not PreviewExposureStore); mark both
  `proxyWidgetMigrationFlush` and `autoExposePreviewNodes` `@internal`.
- proxyWidgetMigration tests: update assertions to verify behavior
  (host inputs, exposures, quarantine entries) now that
  `flushProxyWidgetMigration` returns void.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2812-d683-710e-946f-9ddb9018ff5a
Co-authored-by: Amp <amp@ampcode.com>
2026-05-14 13:28:00 -07:00
Alexander Brown
69bb5bcc6e Merge branch 'main' into drjkl/subgraph-promoted-widget-ratchet-squashed 2026-05-14 11:52:56 -07:00
DrJKL
a7e88c7bc0 chore(eslint): forbid litegraph imports in src/world/
Adds a no-restricted-paths zone enforcing that src/world/ cannot import
from src/lib/litegraph/. The world layer owns canonical entity identity
(WidgetEntityId, value store I/O) and must not depend on litegraph
types or values; this rule prevents accidental coupling.

Two existing imports in entityIds.ts (`NodeId`, `UUID`) are temporarily
suppressed with eslint-disable-next-line + TODO comments:
- NodeId will become a branded EntityId owned by src/world/.
- UUID is a primitive string brand and should move to src/utils/.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2561-ca22-76fa-8356-0c6f92548f8d
Co-authored-by: Amp <amp@ampcode.com>
2026-05-14 11:45:07 -07:00
DrJKL
3c99b75312 fix(appMode): derive entityId for legacy POJO widgets
The appModeStore identity refactor required every selected input to map
to a `widget.entityId`, set by `BaseWidget.setNodeId`. Third-party
extensions that push plain POJO widgets directly onto `node.widgets`
(bypassing `addCustomWidget`) have no `entityId`, so their selections
were dropped from App Mode with a "no canonical identity available"
warning and the linear-widgets panel rendered empty.

Add `getWidgetEntityIdForNode(node, widget)` in `litegraphUtil` that
returns `widget.entityId` when present and otherwise derives
`(rootGraphId, node.id, widget.name)` directly. Apply the helper in:
- `safeWidgetMapper` so the Vue projection includes POJOs
- `appModeStore.findWidgetByEntityId` and the legacy-NodeId branch so
  POJO selections resolve on first selection and on reload

Fixes the `@vue-nodes In App Mode, widget width updates with panel size`
browser test for nodes that use the legacy `node.widgets.push(...)`
pattern.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2561-ca22-76fa-8356-0c6f92548f8d
Co-authored-by: Amp <amp@ampcode.com>
2026-05-14 11:37:39 -07:00
Christian Byrne
daab936d15 feat: add Cloud Status link to website footer (#12237)
*PR Created by the Glary-Bot Agent*

---

## Summary

Adds a "Cloud Status" link to the Contact column of the website footer,
pointing to https://status.comfy.org so users can discover the service
status page from any page on the marketing site.

## Changes

- **What**: New external link in the Contact column of `SiteFooter.vue`,
plus matching i18n keys (`footer.cloudStatus` in `en`/`zh-CN`) and a
`cloudStatus` entry in the `externalLinks` config.

## Review Focus

- Placement: Slotted between Support and Press in the Contact column
(alongside the other support/operational links). Open to moving to the
Resources column instead if preferred.
- URL: `https://status.comfy.org` — assumed convention; swap if a
different status page URL is preferred.
- Also tightened the `contactColumn` type annotation to `{ title:
string; links: FooterLink[] }` to match `companyColumn` / `topColumns`
so the `external: true` field is properly typed.

## Screenshots

Desktop (Contact column, right side):

![Footer desktop](.glary/screenshots/footer-desktop.png)

Mobile (2×2 grid, Contact column bottom-right):

![Footer mobile](.glary/screenshots/footer-mobile.png)

## Screenshots

![Site footer desktop view showing Cloud Status link in the Contact
column with external arrow
icon](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/9ba647494446aa0233ca1f81ce96aa1778a59c5f4bbec394dc8074d4cff611a9/pr-images/1778717012660-56f4efd5-0f20-4570-8a19-0390b573b274.png)

![Site footer mobile view showing Cloud Status link in the Contact
column](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/9ba647494446aa0233ca1f81ce96aa1778a59c5f4bbec394dc8074d4cff611a9/pr-images/1778717013059-fe6803d3-94ae-49ed-bcd9-b1eeee8f5ebd.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12237-feat-add-Cloud-Status-link-to-website-footer-3606d73d36508190a906fdde6d86706d)
by [Unito](https://www.unito.io)

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-14 15:02:41 +00:00
Christian Byrne
f176d18fe0 fix(website): refresh cloud nodes snapshot in release workflow + strict production builds (#12219)
*PR Created by the Glary-Bot Agent*

---

## Summary

`Release: Website` only refreshed the Ashby snapshot, so the cloud-nodes
snapshot (`apps/website/src/data/cloud-nodes.snapshot.json`) was stale
on every release. `loadPacksForBuild()` then silently fell back to that
snapshot because `WEBSITE_CLOUD_API_KEY` was never plumbed through CI or
Vercel, leaving production at `/cloud/supported-nodes` with placeholder
data (e.g. `rgthree-comfy` listed as supported when it isn't — visible
at line 104 of the committed snapshot, last fetched 2026-05-04).

## Changes

- **New composite action `.github/actions/cloud-nodes-pull`** mirroring
`ashby-pull`: runs `pnpm --filter @comfyorg/website
cloud-nodes:refresh-snapshot` with `WEBSITE_CLOUD_API_KEY`. The script
already `process.exit(1)`s on any non-`fresh` outcome, so refresh
failures are loud.
- **`release-website.yaml`** now runs both refreshes and opens a single
PR with both updated snapshots. Renamed the job to `refresh-snapshots`,
updated branch/commit/title/body for the wider scope, and kept the
existing `Release:Website` label so downstream automation is unaffected.
- **`cloudNodes.build.ts`** throws when the outcome is `'stale'` **and**
`VERCEL_ENV === 'production'`. Preview / local builds keep the snapshot
fallback so contributors without key access are unaffected. The CI
reporter still runs first so the GitHub annotation explaining *why* it's
stale is visible in the failed job.
- **`ci-vercel-website-preview.yaml`**: passes `WEBSITE_CLOUD_API_KEY`
to `vercel build` in both preview and production jobs, and adds a
preflight step on `deploy-production` that hard-fails before `vercel
build --prod` if the secret is missing — surfacing config drift with a
maintainer-friendly error annotation instead of mid-build.
- **`apps/website/README.md`**: documents the production-strictness
behavior, the new required secret (GitHub Actions + Vercel env), and the
manual refresh path.
- **New unit tests** in `cloudNodes.build.test.ts` (6 cases): fresh,
stale-no-VERCEL_ENV, stale-on-preview, stale-on-production,
failed-regardless, and "still reports on stale-in-production before
throwing".

## Manual / one-time steps required before merging

This PR cannot finish the job alone. A maintainer must also:

1. Add `WEBSITE_CLOUD_API_KEY` as a **GitHub Actions repo secret** in
`Comfy-Org/ComfyUI_frontend`.
2. Add `WEBSITE_CLOUD_API_KEY` to the **Vercel project environment**
(`production` env at minimum; `preview` recommended).
3. Investigate why `rgthree-comfy` is in the current snapshot — either
the Cloud API was actually returning it on 2026-05-04, the snapshot was
generated against a non-production environment, or it was hand-edited.
The first manual run of `Release: Website` after this PR merges will
confirm.

Without step 1, the new `Release: Website` job will fail loudly (the
refresh script exits 1 with `missing WEBSITE_CLOUD_API_KEY`). Without
step 2, the new preflight will fail the production deploy with a clear
error annotation pointing at `apps/website/README.md`. Both failure
modes are intentional — they replace today's silent stale snapshot.

## Related (out of scope for this PR)

The other half of the original report — production 404s on
`/p/supported-models/*`, `/cloud/supported-nodes/*`,
`/demos/community-workflows` from PRs #11892 / #11903 / #11942 — is a
`comfy-router` allow-list gap (those paths exist in the Vercel build as
pre-rendered static HTML). That fix needs to land in
`Comfy-Org/comfy-router` and is being handled separately since glary
doesn't have access to that repo.

## Verification

- `pnpm --filter @comfyorg/website test:unit` — 75/75 pass (6 new in
`cloudNodes.build.test.ts`)
- `pnpm --filter @comfyorg/website typecheck` — 0 errors, 0 warnings (2
pre-existing hints unrelated to this PR)
- `pnpm format` + `pnpm exec eslint` on changed files — clean
- `js-yaml` validates `release-website.yaml`,
`cloud-nodes-pull/action.yaml`, `ci-vercel-website-preview.yaml`
- Oracle code review (round 1) raised 1 warning + 1 suggestion; both
addressed in commit 2.

**Manual verification not applicable**: the runtime changes are GitHub
Actions workflows and a Vercel-env-gated branch in a build-time module —
they cannot meaningfully run outside of GitHub Actions / Vercel, and the
strict-on-stale path is exhaustively covered by the 6 unit tests
(including the exact assertions a manual run would check: throws on
`VERCEL_ENV=production` + stale, passes on preview, reports
observability annotation before throwing). The end-to-end behavior will
be verified by the first `Release: Website` dispatch and the next
production deploy after the maintainer adds the secret.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12219-fix-website-refresh-cloud-nodes-snapshot-in-release-workflow-strict-production-build-35f6d73d3650816d8f32d403cb39d733)
by [Unito](https://www.unito.io)

---------

Co-authored-by: glary <bot@glary.dev>
2026-05-14 09:49:00 +00:00
Christian Byrne
01742672bb feat: extend version warning to all comfy_package_versions entries (#12167)
*PR Created by the Glary-Bot Agent*

---

## Summary

Extend the existing frontend version-mismatch warning UI to consume the
new `comfy_package_versions` array now exposed by ComfyUI's
`/system_stats` endpoint. The backend ships installed/required versions
for every `comfy*` package pinned in `requirements.txt` (frontend,
workflow-templates, embedded-docs, kitchen, aimdo, …); the frontend now
surfaces one toast per outdated package, reusing the existing N=1
frontend-version warning shape.

Backend PR: https://github.com/Comfy-Org/ComfyUI/pull/13875

## Changes

- `src/schemas/apiSchema.ts` — add `comfy_package_versions: Array<{name,
installed, required}>` to the `SystemStats` schema and export a
`ComfyPackageVersion` type. The field is `.optional()` so older backends
remain compatible.
- `src/platform/updates/common/versionCompatibilityStore.ts` — new
`outdatedComfyPackages` / `packageWarningMessages` computeds that mirror
the existing semver `gt` comparison (same `valid` guard). Skip
`comfyui-frontend-package` because the dedicated frontend warning above
already covers it (and uses the running bundle's version rather than the
installed pip version). Outdated packages are sorted by
`name`/`installed`/`required` before being folded into the dismissal
storage key so the key is stable across response orderings — a fresh
package bump re-shows the warning, but the same outdated set in a
different order does not.
- `src/platform/updates/common/useFrontendVersionMismatchWarning.ts` —
emit one toast per outdated package in addition to the existing frontend
toast, reusing the same i18n wrapper and the existing `hasShownWarning`
once-per-session guard.
- `src/locales/en/main.json` — new `g.comfyPackageOutdated` string.
- Unit tests — added coverage for outdated/skip/invalid-version paths,
dismissal-key inclusion, and stable ordering. Existing 21 store + 8
composable tests untouched and still passing.

CI suppression is unchanged: warnings still gate on
`versionCompatibilityStore.shouldShowWarning` (which respects the
`Comfy.VersionCompatibility.DisableWarnings` setting and the 7-day
dismissal cache), and unit tests continue to mock the store the same
way.

## Verification

- `pnpm vitest run` for the version-warning module: **31/31** passing.
- Targeted sweep across `src/platform/updates`,
`src/stores/systemStatsStore.test.ts`, `src/schemas`: **160/160**
passing.
- `pnpm typecheck`: clean.
- `pnpm lint`: 0 errors (3 pre-existing warnings in unrelated 3D test
files).
- `pnpm format`: applied, no incidental changes.
- **Manual Playwright run** against the real dev server with
`/api/system_stats` intercepted to return outdated package data —
produced exactly the expected toasts and correctly skipped
`comfyui-frontend-package` and the up-to-date `comfy-kitchen` entry.
Same run with all-up-to-date data produced zero toasts.

### Toasts produced (manual verification)

The fixture used: `required_frontend_version=99.99.99`, plus
`comfy_package_versions=[frontend-package (outdated, skipped),
workflow-templates 0.9.0→0.9.5 (outdated), embedded-docs 0.4.0→0.5.0
(outdated), comfy-kitchen 0.2.8→0.2.8 (up to date)]`.

## Screenshots

![Three version warning toasts displayed: outdated frontend (1.45.1 <
99.99.99), outdated workflow-templates (0.9.0 < 0.9.5), and outdated
embedded-docs (0.4.0 < 0.5.0). The skipped comfyui-frontend-package
entry and the up-to-date comfy-kitchen entry produced no extra
toasts.](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/986a510ae8e587d3ced862efffa622d9d476c40b9ac1423b89f56e85c0ce51c1/pr-images/1778562152770-11307c64-2863-41af-a42d-1c42fb332ca6.png)

![Zoomed view of the three Alert toasts in the top-right toast
stack.](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/986a510ae8e587d3ced862efffa622d9d476c40b9ac1423b89f56e85c0ce51c1/pr-images/1778562153211-df80e3b5-48d5-416e-a11f-3a6d25b6618c.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12167-feat-extend-version-warning-to-all-comfy_package_versions-entries-35e6d73d365081e7b993d0f06c9e5c98)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-14 08:24:32 +00:00
github-actions
14e3fd336a [automated] Update test expectations 2026-05-14 08:00:45 +00:00
DrJKL
1a90d32ab9 fix(subgraph): migrate proxyWidgets and preview exposures on paste
`_deserializeItems` previously bypassed both the ADR 0009 proxyWidget
migration and preview-exposure hydration for pasted SubgraphNodes, so
older clipboard data lost promoted widgets and preview exposures.

- Run `proxyWidgetMigrationFlush` for every top-level pasted SubgraphNode
  carrying legacy `properties.proxyWidgets` (nested hosts already covered
  by `LGraph.configure`).
- Topologically sort `parsed.subgraphs` before configuring so nested
  SubgraphNodes' source-resolution sees a fully configured interior.
- Add `remapPreviewExposures` in `remapClipboardSubgraphNodeIds` to
  patch `properties.previewExposures[].sourceNodeId` alongside
  `proxyWidgets` after collision-driven interior id remap.
- Extract `autoExposeKnownPreviewNodes` from `promoteRecommendedWidgets`
  and expose via a new late-bound `LGraph.autoExposePreviewNodes` hook,
  invoked from both `LGraph.configure` and `_deserializeItems` so older
  data without `properties.previewExposures` still derives canvas-image
  previews from known interior node types.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2486-6c35-720c-b983-82df2eda9c7a
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 23:52:15 -07:00
Christian Byrne
b7fe0365af fix: remove "400 Free Credits Monthly" line from cloud paywall modal (#12251)
*PR Created by the Glary-Bot Agent*

---

## Summary

The in-app cloud paywall modal shown on first install of ComfyUI Desktop
1.0 advertised "400 Free Credits Monthly" as a benefit, but the free
tier is currently disabled, making that copy misleading.

Per the thread direction, this skips the feature-flag plumbing (which
isn't available on Desktop anyway) and simply removes the line.

## Changes

- `CloudNotificationContent.vue`: change the feature loop from `n in 4`
to `n in [2, 3, 4]` so feature1 is skipped.
- `src/locales/*/main.json` (12 locales): remove the now-unused
`cloudNotification.feature1Title` key.
- `browser_tests/tests/dialog.spec.ts`: add a regression assertion that
"400" / "Free Credits" copy is no longer present in the modal.

## Test plan

- `pnpm typecheck` — passes
- `pnpm typecheck:browser` — passes
- `pnpm exec vitest run
src/platform/cloud/notification/components/DesktopCloudNotificationController.test.ts`
— 3/3 pass
- Pre-commit hooks (oxfmt, oxlint, eslint, stylelint, typecheck) — all
pass
- Manual: triggered `showCloudNotification()` against the dev server and
confirmed only 3 benefit rows render (screenshot attached); the "400
Free Credits Monthly" line is gone.

- Fixes DESK2-90

## Screenshots

![Cloud paywall modal after fix — shows 3 benefits without the '400 Free
Credits Monthly'
line](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/d96d51ee1f37a5138014eeff4c46a901e5178c013e0412f288e49c17bddb88e4/pr-images/1778728094191-423f01df-f004-4ae5-a939-7d28413f6fbd.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12251-fix-remove-400-Free-Credits-Monthly-line-from-cloud-paywall-modal-3606d73d365081eaa572e6cd995278d8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-13 22:39:32 -07:00
Dante
bdb92c845e fix: include share_id when importing published assets (FE-603) (#12055)
*PR Created by the Glary-Bot Agent*

https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1778150003248989
FE-603
---

## Summary

Send `share_id` alongside `published_asset_ids` from
`workflowShareService.importPublishedAssets`, and type / zod-validate
the body against `ImportPublishedAssetsRequest` +
`zImportPublishedAssetsRequest` from `@comfyorg/ingest-types`.

This is a **parameter-schema change**, not a live bug fix. The original
`BAD_REQUEST: share_id is required` failure is **no longer reproducible
in production** — the backend rolled back the required-field enforcement
in [BE-855](https://linear.app/comfyorg/issue/BE-855) (cloud
[#3587](https://github.com/Comfy-Org/cloud/pull/3587) /
[#3588](https://github.com/Comfy-Org/cloud/pull/3588)). Today the import
succeeds without `share_id`.

- Closes FE-603

## Why we still ship this

Frontend goes first so the backend can later re-tighten the contract
without breaking shared-workflow imports a second time. Agreed sequence
in
[Slack](https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1778535349236419)
— BE-6 was split into two sub-issues to make the chain explicit:

1. **[BE-898](https://linear.app/comfyorg/issue/BE-898)** (blocks
FE-603) — BE makes `share_id` **optional**, validating when present.
[cloud PR #3633](https://github.com/Comfy-Org/cloud/pull/3633), In
Review.
2. **FE-603** (this PR) — FE sends `share_id`.
3. **[BE-899](https://linear.app/comfyorg/issue/BE-899)** (blocked by
FE-603) — BE flips `share_id` back to **required** after FE-603 deploys.

Steps 1 and 2 ship in order: BE-898 → FE-603 → BE-899.

## Changes

- `workflowShareService.importPublishedAssets` now takes `shareId:
string` and types the request body with `ImportPublishedAssetsRequest`.
The body is parsed through `zImportPublishedAssetsRequest` before the
network call so future contract drift surfaces as a typecheck or zod
parse failure rather than a silent runtime 400.
- `useSharedWorkflowUrlLoader.ts` threads `payload.shareId` (already in
scope from `SharedWorkflowPayload`) into the import call.
- Unit tests assert `share_id` is sent and that an empty `share_id` is
rejected before fetch.

## Verification

- `pnpm typecheck` — clean
- `pnpm lint` — 0 errors / 0 warnings on changed files (3 pre-existing
warnings in unrelated files)
- `pnpm format` — applied
- `pnpm vitest run` on both affected files — 34/34 passing

### Live in-browser smoke test (Vite dev server)

Loaded the built module in the browser, intercepted `fetch`, and
exercised the new code path. The new `importPublishedAssets` produces
the correct wire format and zod rejects bad input before fetch:

```json
// Happy path — sent to /api/assets/import (POST)
{
  "published_asset_ids": ["pa-1", "pa-2", "pa-3"],
  "share_id": "share-abc"
}
```

```text
// Empty share_id — zod rejects, fetch is never called
[ { code: "too_small", minimum: 1, path: ["share_id"], message: "String must contain at least 1 character(s)" } ]
fetchCallsBetweenAttempts: 0
```

## Why typecheck didn't catch the original miss

`api.fetchApi(route: string, options?: RequestInit)` accepts the
standard DOM `RequestInit`, so `body` is just `BodyInit | null`. Once
the call site does `JSON.stringify({ ... })`, the inline object is
erased into a string and TS has no schema to enforce. There was no
`@ts-ignore` or `as any` — the generated types were simply never
imported. This PR plugs that one call site; the same pattern should be
applied wherever the frontend hits ingest endpoints (the broader hey-zod
migration gap mentioned in #bug-dump).

Reported in #bug-dump.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12055-fix-include-share_id-when-importing-published-assets-3596d73d365081c0a1c7e69102f5cfcc)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-14 04:13:16 +00:00
DrJKL
ba2a7423c6 fix(subgraph): unify preview projection and editor visibility on SubgraphNode
- Skip LivePreview render on SubgraphNode hosts; previews flow through
  promotedPreviews / previewExposureStore as the single source of truth
- Treat exposures as authoritative in SubgraphEditor.getActivePreviewWidgets
  so $$canvas-image-preview rows always surface, even when the interior
  source node lacks a matching promotable widget
- Hydrate canonical/legacy preview exposures at configure time in
  SubgraphNode._hydratePreviewExposures; usePromotedPreviews is now a
  pure read with path-style nested host fallback
- Add KSampler/KSamplerAdvanced to virtual canvas-image-preview node types
  so live samplers expose preview rows pre-execution
- SubgraphEditor.open() returns early when panel is already visible to
  avoid flaky context-menu opens
- Remove obsolete proxy-widget test; convert inline @vue-nodes name
  prefix to tag annotation across subgraphPromotion.spec.ts
- Drop stale "migrates legacy host exposure keys while reading previews"
  unit tests now that migration runs at configure time

Review feedback fixes:
- useResolvedSelectedInputs: clone graphNodes array to avoid shared
  reference with app.rootGraph.nodes
- previewExposureSchema: avoid mutating the property parameter
- nodeOutputStore: case-insensitive `.svg` check

Amp-Thread-ID: https://ampcode.com/threads/T-019e23c9-3dc1-7381-ae59-c0372b3325e2
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 20:11:06 -07:00
Christian Byrne
d96be3d668 feat(#3410): add centralized assert() utility in src/base/ (#11824)
## Summary

Add a shared `assert(condition, message)` utility in `src/base/` that
centralizes DEV-throw / Desktop-Sentry / nightly-toast / `console.error`
policy for invariant reporting across the codebase.

## Changes

- **`src/base/assert.ts`**: New `assert()` utility with
`setAssertReporter()` registration pattern
  - `console.error` always fires on failure
  - Throws `Error` in DEV mode (surfaces bugs immediately)
  - Delegates to registered reporter otherwise (Sentry, toast, etc.)
- No imports from `platform/` — respects layer architecture (`base →
platform → workbench → renderer`)
- **`src/main.ts`**: Registers Sentry + nightly-toast reporter after
`Sentry.init()`
- **`src/scripts/changeTracker.ts`**: Migrates
`reportInactiveTrackerCall()` to use `assert()`, removing inline
`Sentry.captureMessage` + `console.warn` calls
- **`src/scripts/changeTracker.test.ts`**: Mocks `@/base/assert` to
prevent DEV-mode throws in existing no-op tests

## Testing

### Automated

- `src/base/assert.test.ts` — 6 tests covering: no-op on true,
console.error on false, DEV throw, non-DEV no-throw, reporter
invocation, reporter not called on true
- `src/scripts/changeTracker.test.ts` — 16 tests all pass (pre-existing)
- Coverage: 100% for assert.ts

### E2E Verification Steps

1. Run `pnpm test:unit` — all tests pass
2. Build the app and open browser devtools
3. In DEV mode: trigger a lifecycle violation (call an inactive tracker
method) — should see error thrown in console
4. In production build: same trigger — should see `console.error` only,
no throw

## Review Focus

- `setAssertReporter()` is called in `main.ts` once at startup —
appropriate for a singleton reporter. In tests that import `assert`, the
reporter is reset to a no-op in `afterEach`.
- Layer architecture respected: `base/assert.ts` has zero imports, upper
layers wire in side effects via `setAssertReporter()`.

Fixes #11373

<!-- Pipeline-Ticket: pick-issue-3410 -->

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11824-feat-3410-add-centralized-assert-utility-in-src-base-3546d73d3650819d96afdf4018161c26)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Connor Byrne <c.byrne@comfy.org>
2026-05-14 02:42:16 +00:00
Dante
1869416185 fix: surface error dialog when Open Workflow from Job Queue fails (FE-215) (#12071)
## Summary

`openJobWorkflow` (Job Queue → "Open Workflow in New Tab") had no error
handling around `workflowService.openWorkflow`. When workflow JSON
contained nodes that fail `LiteGraph.configure()` (e.g. rgthree
`DisplayAny`, stale `GetNode/SetNode aux_id`), the menu action appeared
to do nothing — either an early return after a generic "Load Workflow
Error" dialog (`app.ts:1340-1347`) or a context-less toast from the
surrounding `wrapWithErrorHandlingAsync`.

This PR catches the error inside `openJobWorkflow` and routes it to
`dialogService.showErrorDialog` with a Job-Queue-specific `reportType`
so users get a clear, actionable message tied to the action they
invoked.

- Fixes #8841
- Linear:
[FE-215](https://linear.app/comfyorg/issue/FE-215/open-workflow-from-frontend-not-working)

## Red-Green Verification

| Commit | CI Status | Run |
|--------|-----------|-----|
| `test: add failing test for openJobWorkflow swallowing load errors`
(a422b392d) | 🔴 failure |
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/25531063427 |
| `fix: surface error dialog when Open Workflow from Job Queue fails`
(86b2a3a9) | 🟢 success |
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/25531374096 |

## Test Plan

- [x] CI red on test-only commit
- [x] CI green on fix commit
- [ ] Manual: load a workflow containing rgthree `DisplayAny` from Job
Queue → "Open Workflow in New Tab" → confirm error dialog appears with
Job-Queue context instead of nothing
<img width="972" height="662" alt="Screenshot 2026-05-13 at 5 58 31 PM"
src="https://github.com/user-attachments/assets/bf1d8d96-85b7-47a8-89d8-b3bb69a526cb"
/>
2026-05-14 02:32:32 +00:00
jaeone94
a3106c4d53 fix: open node info panel from context menu (#12205)
## Summary

Replaces #12164.

Right-clicking a Vue node, using the selection toolbox More Options
menu, or clicking the selection toolbox Node Info button now opens the
right-side Info tab only when the new-menu UI makes that panel
available. Legacy-menu contexts hide the no-op action even when the
legacy node library design is selected; node-library help remains
isolated to the node library itself. The existing
`selection_toolbox_node_info_opened` telemetry fires only after the
toolbox button successfully opens node info. No new context-menu
telemetry event is added in this PR.

## Changes

- **What**: Share the node-info availability/action path across the
context menu and selection toolbox, keep legacy-menu state out of the
right-side panel public store API, tighten node-info settings tests, and
add unit plus E2E regression coverage for new-menu and legacy-menu
modes.
- **Dependencies**: None

## Review Focus

Confirm the node context menu, selection toolbox direct Info button, and
selection toolbox More Options entry all respect right-side panel
availability, including legacy menu + legacy node library mode, while
node-library help behavior remains isolated to the node library.

## Validation

- Self-review: checked production path, unit mocks, and Playwright
coverage; only gap found was weak E2E coverage for the toolbox direct
Info path, now strengthened.
- `pnpm test:unit -- src/composables/graph/useSelectionState.test.ts
src/components/graph/SelectionToolbox.test.ts
src/components/graph/selectionToolbox/InfoButton.test.ts`
- `pnpm test:browser:local -- --project=chromium
browser_tests/tests/selectionToolboxActions.spec.ts
browser_tests/tests/selectionToolboxSubmenus.spec.ts
browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts
--grep "info button opens the right-side info tab|info button is
hidden|hides Node Info|should open node info"`
- `pnpm typecheck:browser`
- `pnpm exec oxlint --type-aware
browser_tests/tests/selectionToolboxActions.spec.ts`
- `pnpm exec eslint --cache --no-warn-ignored
browser_tests/tests/selectionToolboxActions.spec.ts`
- `pnpm exec oxfmt --check
browser_tests/tests/selectionToolboxActions.spec.ts`
- `git diff --check`
- Commit hooks: lint-staged + `pnpm typecheck` + `pnpm
typecheck:browser`
- Push hook: `knip --cache` (existing tag hint only)

## Screenshots (if applicable)
Before 


https://github.com/user-attachments/assets/4b1f6ddb-a01c-4958-81ab-36167f434e59


https://github.com/user-attachments/assets/83433f0d-24f1-46b7-a81d-f0f065812496

After 


https://github.com/user-attachments/assets/30bd61e5-f8d4-48b7-97e0-26c93e3cb362


https://github.com/user-attachments/assets/afce9f51-a43d-434f-a006-6b357a61ac8f

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-05-14 02:26:11 +00:00
Comfy Org PR Bot
b59b342a43 1.45.7 (#12238)
Patch version increment to 1.45.7

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12238-1-45-7-3606d73d36508138981ee641470344e4)
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>
v1.45.7
2026-05-14 02:21:20 +00:00
jaeone94
95e616b894 fix: clear media upload errors via widget change (#12212)
## Summary

Clear missing media validation errors after paste/drop media uploads by
emitting the existing widget-change event path.

## Changes

- **What**: Emit `node.onWidgetChanged` after image/video upload
completion updates the file combo widget.
- **What**: Emit the same widget-change path after Load Audio upload
completion.
- **What**: Add unit coverage for upload completion emitting
`onWidgetChanged` and for missing media clearing through that existing
hook path.
- **What**: Add E2E coverage for Load Image drag/drop and paste clearing
validation rings, with red/green verified from a fresh `main` base.
- **Dependencies**: None.

## Review Focus

Please check that paste/drop upload paths now reuse the existing
widget-change error-clearing path instead of expanding `widget.callback`
patching.

Also check the Load Image E2E helper path for synthetic paste/drop
behavior.

Supersedes #12207.

Ref: FE-687

## Screenshots

Before 


https://github.com/user-attachments/assets/2cee52bc-b1c8-4dff-8a02-5b18a69ae639

After 


https://github.com/user-attachments/assets/e1ecd147-1d8a-470e-b77d-13345d473ef3



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12212-fix-clear-media-upload-errors-via-widget-change-35f6d73d365081bcb1a0dfc042d417eb)
by [Unito](https://www.unito.io)
2026-05-14 01:28:41 +00:00
Alexis Rolland
5738c7a539 Add SaveAudioAdvanced to whitelisted nodes (#12213)
## Summary

Add `SaveAudioAdvanced` to whitelisted nodes in order to display the
audio player. This PR goes with the core PR:
https://github.com/Comfy-Org/ComfyUI/pull/13871

## Changes

- **What**: Display audio player on new node `SaveAudioAdvanced`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12213-Add-SaveAudioAdvanced-to-whitelisted-nodes-35f6d73d3650815783fac52d3d37e1e1)
by [Unito](https://www.unito.io)
2026-05-14 00:10:06 +00:00
DrJKL
293eea446f test(appMode): match widget key by nodeId:name suffix of canonical entityId
Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 16:34:24 -07:00
DrJKL
9bcb4c3df2 test(vueNodes): assert canonical entityId forwarded to AppInput
Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 16:32:32 -07:00
Christian Byrne
8abfa678a3 fix: harden e2e coverage workflow and fix GH Pages deploy (#11381)
## Problem

The GH Pages coverage deploy has been failing since #11291 merged —
every `CI: E2E Coverage` workflow run errors out and
https://comfy-org.github.io/ComfyUI_frontend/ returns 404.

Additionally, two correctness/security issues were identified in the
workflow (filed as #11374 and #11375).

## Changes

1. **`--ignore-errors source` on genhtml** — merged LCOV data includes
paths like `localhost-8188/assets/main-BRkC1B8m.js` from Playwright V8
coverage instrumented runtime bundles that don't exist as source files
in CI, causing genhtml to error out
2. **Pin checkout to `workflow_run.head_sha`** — in `workflow_run`
context, the default checkout ref points to the default branch, not the
commit that triggered the upstream run; genhtml could annotate against
wrong source files (#11375)
3. **Gate deploy on `event == 'push'`** — a fork branch named `main`
could satisfy the branch check and overwrite production coverage; adding
the event guard prevents this (#11375)
4. **Include workflow run link in placeholder HTML** — when no coverage
data is available, the placeholder page now links back to the workflow
run for debugging (#11374)

## Fixes

- Fixes the GH Pages 404 caused by #11291
- Fixes #11374
- Fixes #11375
2026-05-13 23:06:36 +00:00
Christian Byrne
b36b601a1c refactor(litegraph): prune dead surfaces (AUDIT-LG implementation, draft) (#12228)
Implements the AUDIT-LG verdicts (#12223, #12224, #12225) as a single
deletion PR off main.

> **DRAFT — sequencing.** Per the AUDIT-LG framing doc, deletions land
**after Phase B ECS migration** (Alex's #11939 + #11811). Open early to
capture the diff and let CI cascade-flag any unused-export fallout. Flip
to ready-for-review post-Alex-rebase.

## Status

| commit | scope | status |
|---|---|---|
| 1 | Delete 6 LGraph stepping hooks (`onAfterStep`, `onBeforeStep`,
`onPlayEvent`, `onStopEvent`, `onAfterExecute`, `onExecuteStep`) + their
dispatch sites in `start()`/`stop()`/`runStep()` |  landed (this
commit) |
| 2 | Delete the rest of the dead executor cluster
(`start()`/`stop()`/`runStep()`/`sendEventToAllNodes()` + state fields +
`STATUS_*` constants) | follow-up |
| 3 | Delete `LGraphNode` dead event hooks (~22 fields per #12224) |
follow-up |
| 4 | Delete trigger/action subsystem (~22 symbols, #12223) | follow-up
|
| 5 | `ON_EVENT` deprecation cycle, release N (#12225) | follow-up |

## Verification (commit 1)

```
$ pnpm lint && pnpm format:check && pnpm knip
✓ format: All matched files use the correct format
✓ lint: pre-existing icon-name warnings only
✓ knip: no new unused exports flagged
```

## Verdicts the deletion is grounded in

- AUDIT-LG.7 master verdict table (146 surfaces classified DELETE-NOW /
DEPRECATE / KEEP)
- AUDIT-LG.9 per-symbol attribution sweep (confirmed zero functional
callers for the trigger cluster + the dead hooks)

For each surface in this PR:

- **`internal_count` from rg over `src/`, `browser_tests/`, `packages/`
excluding `lib/litegraph/`:** 0
- **External use from touch-points DB:** 0 (per AUDIT-LG.3 + AUDIT-LG.9
per-symbol attribution)
- **Host methods (`start()`/`stop()`/`runStep()`):** `@deprecated 'Will
be removed in 0.9'` already

The dispatch sites (`this.onAfterStep?.()` etc.) are inside the
deprecated host methods — removing the dispatchers does not change
observable behaviour because no listener is attached to begin with.

## Why batched, why draft

Per AUDIT-LG framing, the deletion sequences behind Alex's PR #11939
(ECS world-combo). Opening as draft lets CI run early and flags any
unused-export cascades the audit script missed. Each follow-up commit
will be its own atomic deletion (one feature per commit) so any single
one can be reverted in isolation if needed.

cc @drjkl @christian-byrne

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12228-refactor-litegraph-prune-dead-surfaces-AUDIT-LG-implementation-draft-35f6d73d365081e0b72cd292228e2ca6)
by [Unito](https://www.unito.io)

Co-authored-by: Connor Byrne <c.byrne@comfy.org>
2026-05-13 23:03:32 +00:00
DrJKL
e9db30bfd3 fix(subgraphEditor): exclude promoted widgets from candidate list
The candidate filter compared `toKey()` outputs across the active
section (PromotedWidgetView, key includes `:sourceNodeId` suffix) and
the interior section (raw widget, no suffix). The shapes never matched,
so every link-promoted widget kept showing in the Hidden section as
`icon-eye/eye-off`. Compare on the interior `(node.id, widget.name)`
identity instead.

Update the Properties panel test to demote a link-promoted widget via
the source-node context menu (since linked promotions render
`icon-link` and disable the editor toggle by design).

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 15:58:02 -07:00
jaeone94
ad63f7cb9b test: cover missing media runtime sources (#12126)
## Summary

Adds browser coverage for the missing-media runtime paths introduced by
#12069 and #12111:

- OSS: annotated `[output]` media is resolved from job history.
- Cloud: compact `[output]` media is resolved from output assets.
- OSS and Cloud: dropped video uploads do not surface a missing-media
error while the upload is still in progress.

This PR is now rebased directly onto `main`; the parent fix PRs have
been squash-merged, so this branch only contains the E2E coverage
commit.

## Test Fixtures

- Adds workflow fixtures for OSS spaced output annotations and Cloud
compact output annotations.
- Adds a small plain MP4 fixture for video drag/drop upload coverage.

## Validation

- `pnpm exec oxfmt --check
browser_tests/tests/propertiesPanel/errorsTabMissingMediaRuntime.spec.ts
browser_tests/assets/missing/missing_media_cloud_output_annotation.json
browser_tests/assets/missing/missing_media_output_annotations.json`
- `pnpm typecheck:browser`
- `pnpm exec oxlint
browser_tests/tests/propertiesPanel/errorsTabMissingMediaRuntime.spec.ts
--type-aware`
- `git diff --check origin/main..HEAD`

Note: before the rebase, the Cloud project target for this spec passed
locally. Local OSS project execution against the currently running Cloud
dist did not reach the test body because `ComfyPage.waitForAppReady`
timed out in `beforeEach`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12126-test-cover-missing-media-runtime-sources-35d6d73d365081f0a981c02f33c0ff84)
by [Unito](https://www.unito.io)
2026-05-13 22:46:47 +00:00
Terry Jia
f7ef563b46 FE-657: prevent browser zoom on ctrl+wheel in mask editor (#12215)
## Summary

Wheel events on the mask editor pointer zone now call preventDefault,
matching the main canvas behavior so ctrl+wheel only zooms the mask
canvas instead of also triggering page zoom.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12215-FE-657-prevent-browser-zoom-on-ctrl-wheel-in-mask-editor-35f6d73d36508131a9b8dbf2f6640d72)
by [Unito](https://www.unito.io)
2026-05-13 18:24:22 -04:00
DrJKL
d699ce603e Merge remote-tracking branch 'origin/main' into drjkl/subgraph-promoted-widget-ratchet-squashed 2026-05-13 15:14:29 -07:00
DrJKL
301d071255 fix(appBuilder): show displayName on unknown selected-input rows
The unknown-status branch of the input-select sidebar was binding
the title to the encoded entityId (e.g. graphId:nodeId:name) instead
of the persisted displayName. Users (and the appModePruning test)
saw an unreadable id where the original widget name should appear.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 15:12:57 -07:00
DrJKL
d10e133b72 fix(appMode): persist canonical entityId on click and keep dangling entries
AppInput's togglePromotion was pushing a bare nodeId tuple into
selectedInputs. The new useResolvedSelectedInputs projection drops
non-canonical ids, so clicks in App Builder never produced sidebar
entries. Forward widget.entityId through useProcessedWidgets and
NodeWidgets so AppInput can persist the canonical WidgetEntityId.

upgradeAndValidateInput also pruned any entityId whose widget was
missing, even when the host node still existed. Restore the
node-exists fallback so dynamic widgets surface as 'Widget not visible'
rows instead of being silently dropped on load.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 14:32:42 -07:00
DrJKL
560a1ef8ec test(vueNodes): add distinct widgets via addWidget
Pushing the same widget reference now collapses to one render row
because dedupe keys on entityId. Use addWidget with unique names so
the test exercises real reactive add behavior.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 13:46:28 -07:00
AustinMroz
9cc09cd46c Add additional subgraph test fixtures and tests (#11806)
- Adds functions to SubgraphHelper to perform widget promotion by
standard user means
  - Right Click -> Promote
  - Properties Panel
- Adds new slot fixture code that works with simple `locator.dragTo`
operations.
- Adds multiple subgraph tests with a focus on historically difficult
operations.
- Fixes a bug where the litegraph `node.selected` state would not be
unset when switching graphs. This made it so 'Selecting a node ->
leaving subgraph -> re-enter subgraph -> right click on node' would fail
to select the node because it is marked as already selected.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11806-Add-helper-functions-for-widget-promotion-3536d73d365081f58dd9cd730c1a91a9)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-13 20:35:57 +00:00
pythongosssss
de1c1ee1f2 fix: add support for parsing python generated json with NaN/infinite (#12217)
## Summary

API and other legacy JSON generated by python `json.dumps` can contain
`NaN` and `Infinity` which cannot be parsed with JS `JSON.parse`. This
adds regex to replace these invalid tokens with `null`.

## Changes

- **What**: 
- add regex replace on bare NaN/infinity tokens after JSON.parse fails
- update call sites
- tests

## Review Focus
- The regex should only rewrite bare NaN/-Infinity/Infinity and not
touch string values or other invalid tokens.
- A small regex was chosen over JSON5 due to package size (30.3kB
Minified, 9kB Minified + Gzipped) or a manual parser due to the
unnecessarily complexity vs a single regex replace.
- The happy path is run first, the safe parse is only executed if that
failed, meaning no overhead the vast majority of the time and no
possiblity of corrupting valid workflows due to a bug in the fallback
parser
- Multiple call sites had to be updated due to pre-existing architecture
of the various parsers, an issue for unifying these is logged for future
cleanup
- New binary fixtures added for validating e2e import using real files

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12217-fix-add-support-for-parsing-python-generated-json-with-NaN-infinite-35f6d73d365081889fc7f4af823f29c1)
by [Unito](https://www.unito.io)
2026-05-13 20:33:19 +00:00
AustinMroz
86b1e1a965 Fix descriptions on core blueprints (#12220)
Core blueprints were storing the description under a different key than
expected, which resulted in them displaying a placeholder description.
When initializing the description for a subgraph, this alternative field
is also checked.

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/ed51c4a8-00cf-4927-9cba-880532a9e926"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/f19bf80d-adcc-4e9b-a9ba-a5ac8e089e2d"
/>|

Resolves FE-681

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12220-Austin-blueprint-descriptions-35f6d73d3650812fa04df48c203bebd1)
by [Unito](https://www.unito.io)
2026-05-13 20:30:45 +00:00
DrJKL
8273c97b2f refactor(appMode): resolve selectedInputs once at the computed boundary
Introduce `useResolvedSelectedInputs` that maps each persisted
`[entityId, displayName, config?]` entry to a discriminated union of
`{ status: 'resolved', node, widget, ... } | { status: 'unknown', ... }`.
Handlers (rename, remove, resize, bounding) now receive widget instances
directly instead of re-walking `rootGraph.nodes` per interaction.

- Delete `src/world/widgetLookup.ts`; the lone remaining caller
  (`upgradeAndValidateInput` in `appModeStore.ts`) inlines the lookup as a
  module-private function.
- `inlineRenameInput` deleted; the template calls `renameWidget(widget,
  node, $event)` directly with the in-scope widget.
- `getWidgetBounding` accepts a `ResolvedSelection`; returns `undefined`
  for unresolved entries.
- `useAppModeWidgetResizing.onPointerDown` and `updateInputConfig` accept
  a widget instance, matching the existing `removeSelectedInput` shape.
- Persisted `selectedInputs` shape unchanged.
- Unresolved entries continue to render as a removable "unknown widget"
  pill in the sidebar so users can clean up dangling selections.

Slot-rename → entityId-drift bug investigated and confirmed not present:
the `renaming-input` handler in `SubgraphNode.ts` mutates `widget.label`
and `input.label`, never `widget.name`, so `entityId` is stable across
renames. Documented as an invariant in `useResolvedSelectedInputs`.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 13:07:33 -07:00
pythongosssss
4321013798 fix: resolve widget input link position drift on reload (#12214)
## Summary
The position of a link relative to its slot was able to drift on load,
due to widgets inside a node being able to resize without triggering an
node-level resize event (min-height node with space at the bottom could
have widgets expand into free space, causing misalignment).

Recreation:
1. Add KSampler
2. Add Float
3. Connect Float to KSamper.denoise
4. Reload workflow (F5)
5. Observe misalignment

## Changes

- **What**: 
- track widget grid element as signal only that triggers resync
- node bound calculations skipped for widget signals
- prevent setDirty on non-graph nodes (e.g. LGraphNodePreview)
- tests

## Review Focus
This is a small focused approach to fix the reported issue - it does not
address the underlying issue of the layout not being a SSOT. This fix is
a small bandaid and investigation into resolving the layout SOT issue is
not impacted by this.

## Screenshots (if applicable)

Before:
<img width="673" height="374" alt="image"
src="https://github.com/user-attachments/assets/2d34b8e3-0731-4fd2-8553-4dd429010ced"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12214-fix-resolve-widget-input-link-position-drift-on-reload-35f6d73d3650814eb31bebb3042ff58b)
by [Unito](https://www.unito.io)
2026-05-13 20:00:50 +00:00
pythongosssss
7ce0973386 fix: prevent first user template popup when following shared link (#12024)
## Summary

When a user who has not used the app before first loads up, they are
presented with the template selection dialog. This conflicts when the
first-time user visits the app via a share link - both the share &
template dialog are triggered.

## Changes

- **What**: 
- Skip the templates browser when share param is in URL
- Tests
- Add `url` to `setup`/`goto` to allow specifying the `share` parameter

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12024-fix-prevent-first-user-template-popup-when-following-shared-link-3586d73d365081cbbcecdba45a1ad1ea)
by [Unito](https://www.unito.io)
2026-05-13 19:19:54 +00:00
DrJKL
302693bcd9 test(subgraph): fix promoted widget store-key assertions
Drop the local `getHostStateName` helper that reconstructed the now-deleted
encoded `[name, sourceNodeId, sourceWidgetName].join(':')` format. Use
`widget.name` directly — the canonical store key after the entityId
migration in c1515374b. Fixes 2 failing tests in
SubgraphWidgetPromotion.test.ts.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 12:14:31 -07:00
DrJKL
06614003f2 refactor(widgetValueStore): deprecate triple-keyed get/register
Steer new callers to `widgetValueIO`'s entityId-keyed helpers
(`getWidgetState`, `readWidgetValue`, `ensureWidgetState`). The branded
`WidgetEntityId` makes producer/consumer drift over `(graphId, nodeId, name)`
a type error rather than a silent value mix-up — this was the root cause of
the PR #12197 promoted-widget rendering bugs.

JSDoc-only; no behavior change.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 12:07:20 -07:00
DrJKL
c1515374b8 refactor(subgraph): key promoted widget state by entityId, drop encoded host-state name
Replace the encoded `[name, sourceNodeId, sourceWidgetName].join(':')`
host-state-name with the canonical `WidgetEntityId` per ADR 0009. Producers
and consumers flip in the same diff:

- Delete `getPromotedWidgetHostStateName` (was internal disambiguator)
- `PromotedWidgetView` reads/writes via `widgetValueIO` keyed by `entityId`
- `SubgraphNode.serialize` reads via `readWidgetValue(widget.entityId)`
- `getExplicitHostWidgetValue` drops its now-unused `subgraphNode` param
- `safeWidgetMapper` no longer overrides `storeNodeId/storeName`; those
  fields are removed from `SafeWidgetData`
- `useProcessedWidgets` looks up state via `getWidgetState(entityId)`
- `getWidgetIdentity` collapses to `${entityId}:${type}` (type suffix
  preserves the PR #9896 invariant for node-def schema drift)

Net -66 LOC. No fallback / accept-either branches: entityId is the single
source of truth for widget identity across the renderer.

Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 11:57:29 -07:00
Yourz
6e9be7b164 feat: add Anthropic partner icon (#12216)
*PR Created by the Glary-Bot Agent*

---

## Summary

Adds the Anthropic logo to the partner-node icon set so nodes whose
category ends in `Anthropic` (e.g. the Claude node added in
Comfy-Org/ComfyUI#13867 with `category="api node/text/Anthropic"`)
render the correct provider badge in the node library.

## Changes

- `packages/design-system/src/icons/anthropic.svg` — new auto-discovered
partner icon (Anthropic A glyph, sourced from
[lobehub/lobe-icons](https://github.com/lobehub/lobe-icons), uses
`fill="currentColor"` for theme adaptation)
- `src/utils/categoryUtil.ts` — register Anthropic's brand coral
`#D97757` as the badge border color
- `packages/design-system/src/css/style.css` — add `anthropic` to the
dynamic comfy-icon safelist so Tailwind/Iconify emits CSS for
`icon-[comfy--anthropic]` in production builds
- `src/utils/categoryUtil.test.ts` — regression tests for
`getProviderIcon('Anthropic')` and `getProviderBorderStyle('Anthropic')`

## Verification

- `pnpm typecheck` ✓
- `pnpm lint` ✓ (0 errors; 3 pre-existing warnings in unrelated files)
- `pnpm format:check` ✓
- `pnpm test:unit -- src/utils/categoryUtil.test.ts` ✓ (13/13)
- `pnpm build` ✓ — confirmed `comfy--anthropic` class is emitted into
`dist/assets/index-*.css`
- Manual visual check via Playwright against `pnpm dev`: injected `<i
class="icon-[comfy--anthropic]">` elements at badge size (10px) and 48px
alongside the existing OpenAI and BFL icons and confirmed the Anthropic
"A" glyph renders correctly in coral. See screenshot.

End-to-end visual verification of the live badge in the node library
requires Comfy-Org/ComfyUI#13867 to land first (the Claude node is what
produces the `Anthropic` category that triggers the icon lookup).

Related: Comfy-Org/ComfyUI#13867

## Screenshots

![Anthropic icon rendered in coral alongside OpenAI and BFL partner
icons at badge and large
sizes](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/c0076decedd8863eec6253b44e583da6b3eaacc20081d126aaf5267c72c8cc84/pr-images/1778683078329-49e37a7b-86ed-4ef2-988f-5702433f8412.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12216-feat-add-Anthropic-partner-icon-35f6d73d36508133a134fcafaf72f4f8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-13 17:01:49 +00:00
DrJKL
9233025ab4 refactor(appMode): unify widget identity via WidgetEntityId
Introduce a canonical `WidgetEntityId` (`graphId:nodeId:name`) sourced
from the widget instance itself, replacing the ad-hoc tuple plus
NodeLocatorId scheme used by app-mode selection.

- Add `src/world/` with branded `WidgetEntityId`, lookup by id, and
  value I/O helpers.
- Expose `entityId` on `IBaseWidget` / `BaseWidget` and
  `PromotedWidgetView`; surface it through the Vue widget mapper.
- Migrate `appModeStore` selection tuples and pruning to entity-id
  identity; legacy NodeId / NodeLocatorId tuples are upgraded forward.
- Update `AppBuilder`, `AppModeWidgetList`, and resize composable to
  consume entity ids instead of (nodeId, widgetName) pairs.

Also fix a multi-host primitive bypass bug in `proxyWidgetMigration`:
host values now land on each host's input mirror, never the shared
interior consumer widget, and a marker on the primitive lets later
hosts of the same subgraph reuse the bypassed `SubgraphInput` after
the first host severs the primitive's outputs.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019e2235-1469-7501-bad7-388dcb5b4a29
2026-05-13 09:50:17 -07:00
Terry Jia
4b5b184cad FE-566: fix Painter mask submission edge cases on cloud (#12196)
## Summary
Rework the painter always hands the backend a valid asset reference:
- Drop the `hasStrokes` flag and the `isCanvasEmpty` check.
- `serializeValue` falls back to the existing `modelValue` when the
canvas element is transiently unmounted, reuses the cached upload when
not dirty and a value is present, and otherwise uploads the current
canvas (a fully transparent PNG is a valid no-op mask, Painter's Python
`execute()` treats painter_alpha=0 the same as "no mask painted").
- `handleClear` now also clears `modelValue` so a user-initiated clear
doesn't resurrect a stale upload on the next serialize.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12196-FE-566-fix-Painter-mask-submission-edge-cases-on-cloud-35e6d73d365081dd8856ddb785952526)
by [Unito](https://www.unito.io)
2026-05-13 10:19:39 -04:00
pythongosssss
129bfd9f1b fix: fix drop location and zindex of dragged in images (#12194)
## Summary

Images dragged into the canvas were placed at the last graph mouse
position, which is not updated during the drag event - meaning nodes
were created in "random" locations. Additionally, the z-index was not
set so newly created nodes can appear under other nodes.

## Changes

- **What**: 
- ensure added nodes are at top level
- update graph mouse pos with position from drop event
- tests

## Screenshots (if applicable)


Before / After

https://github.com/user-attachments/assets/34b4652e-a834-4c22-b191-2875a2404ac5

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12194-fix-fix-drop-location-and-zindex-of-dragged-in-images-35e6d73d3650814781edc9f4b4b5b223)
by [Unito](https://www.unito.io)
2026-05-13 10:35:16 +00:00
Christian Byrne
e60ae14bc0 feat(website): hide Free tier behind SHOW_FREE_TIER flag (#12165)
*PR Created by the Glary-Bot Agent*

---

## Summary

Disables the Free tier on the public marketing website
(`comfy.org/cloud/pricing` and `comfy.org/cloud`) behind a single
boolean flag so re-enabling is a one-line change.

The Free tier was already removed from the Comfy Cloud sign-up flow;
this PR removes the matching promotional surfaces on the marketing site
so users hit the paywall directly.

## Changes

- **New** `apps/website/src/config/features.ts` — exports
`SHOW_FREE_TIER` (currently `false`). Flip to `true` to restore the
previous UX.
- **`apps/website/src/components/pricing/PriceSection.vue`** — when
`SHOW_FREE_TIER` is `false`:
  - drops the Free plan card from the pricing array
  - desktop grid collapses from `lg:grid-cols-4` to `lg:grid-cols-3`
- Standard plan's "Everything in Free, plus:" intro is replaced with an
aria-hidden spacer so the three remaining cards stay vertically aligned
- **`apps/website/src/components/product/cloud/PricingSection.vue`** —
hides the "Start free. Upgrade when you're ready." tagline on the
`/cloud` pricing teaser.
- **New** `apps/website/e2e/pricing.spec.ts` — three @smoke tests
asserting the paid tiers + Enterprise are visible and that all Free-tier
surfaces are absent.

All translation strings (`pricing.plan.free.*`, `cloud.pricing.tagline`)
are retained so re-enabling requires no copy work.

## Verification

- `pnpm typecheck` — clean (0 errors, 0 warnings; pre-existing hint
unrelated)
- `pnpm lint` / `oxfmt` — clean
- `pnpm test:unit` — 30/30 passing
- Playwright e2e (desktop project) — `pricing.spec.ts` 3/3 passing,
`cloud.spec.ts` 13/13 still passing
- Visual: desktop and mobile pricing pages render with balanced 3-column
layout; `/cloud` teaser card still proportional.

Screenshot below.

## Screenshots

![/cloud/pricing desktop with Free tier hidden: balanced 3-column layout
(Standard, Creator, Pro) and Enterprise section
below](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/732492b43e4794dae051b2b86643e7a5d3daebce028765f27470561681d4f1db/pr-images/1778561413584-9c1a82d1-781f-49d4-9a26-cc7523c903c4.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12165-feat-website-hide-Free-tier-behind-SHOW_FREE_TIER-flag-35e6d73d36508164b4dfcfe9fee6b5e7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-05-13 05:40:09 +00:00
DrJKL
acd71a6085 refactor: consolidate Outcome types and replace push loops with flatMap
- proxyWidgetMigration.ts: collapse three *Result discriminated unions
  into a single Outcome<TOk, TReason>; merge quarantineFor into
  makeQuarantineEntry; SnapshotLink extends PrimitiveBypassTargetRef;
  rename cohortReferencesPrimitive -> cohortDuplicatesPrimitive and
  drop mutable counter
- SubgraphNode.serialize: replace for+push+hasSerializableValue flag
  with flatMap and .some()
- appModeStore: replace nested for+continue+push with nested flatMap

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 22:37:06 -07:00
DrJKL
aacb3eb5c3 refactor(usePromotedPreviews): extract helpers, drop mutable accumulators
- Add loadOrMigrateExposures(rootGraphId, primary, ...legacy) used by
  both top-level host resolution and resolveNestedHost; deduplicates
  the 'try primary -> fall back -> migrate to primary' pattern.
- Add readReactivePreviewUrls() that bundles the 4-way reactive
  dependency reads with the URL fetch fallback, keeping the comment
  about Vue dependency tracking next to the code that depends on it.
- Replace the 3-way previewMediaType ternary with a lookup map +
  default via getPreviewMediaType().
- Replace the imperative for-loop + previews.push() + continue chain
  with exposures.flatMap(); eliminates the last 'let' in the function.
- Drop the exposurePairs rename map; read exposure.name /
  exposure.sourceNodeId / exposure.sourcePreviewName directly.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 21:11:02 -07:00
DrJKL
d3750d7cba test(usePromotedPreviews): collapse hoisted mocks to inline singleton
Replace five separate vi.hoisted(() => vi.fn()) declarations plus the
useNodeOutputStoreMock factory with a single inline vi.mock factory
that owns the singleton store. Tests configure individual methods via
vi.mocked(useNodeOutputStore().method), matching the project's
vitest-patterns guidance and the pattern used in useGLSLPreview.test.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 21:02:56 -07:00
Comfy Org PR Bot
b172534f55 [chore] Update Ingest API types from cloud@9619326 (#12202)
## Automated Ingest API Type Update

This PR updates the Ingest API TypeScript types and Zod schemas from the
latest cloud OpenAPI specification.

- Cloud commit: 9619326
- Generated using @hey-api/openapi-ts with Zod plugin

These types cover cloud-only endpoints (workspaces, billing, secrets,
assets, tasks, etc.).
Overlapping endpoints shared with the local ComfyUI Python backend are
excluded.

---------

Co-authored-by: MillerMedia <7741082+MillerMedia@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-05-13 03:47:03 +00:00
DrJKL
ed93beaeef test(usePromotedPreviews): clean up mocks, parameterize media-type cases
- Replace vi.clearAllMocks() + four explicit mockReset() calls with
  single vi.resetAllMocks().
- Drop dead nodeOutputStore variable; inline the per-test mock store
  assignment.
- Parameterize video/audio media-type cases via it.for.
- Rename the previously misleading 'uses preview exposures by source
  preview name' test to clearly target the default-image branch when
  previewMediaType is unset.
- Extract arrangePromotedPreview() helper to collapse repeating four-line
  setup boilerplate.
- Replace '$$canvas-image-preview' magic strings with the existing
  CANVAS_IMAGE_PREVIEW_WIDGET constant.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 20:40:56 -07:00
DrJKL
4fa8948959 test(subgraph): tag new tests @vue-nodes and add DOM assertions
Audit finding: 3 of 4 new tests in this PR either lacked the @vue-nodes
tag or relied solely on canvas/state assertions. Bring them in line with
the rest of the @vue-nodes coverage:

- 'Legacy primitive proxy widgets migrate...' — add @vue-nodes tag and
  assert host renders 2 migrated widget rows before and after reload.
- 'Nested preview exposures render...' — add explicit .lg-node-widgets
  absence assertion (preview-only host has no widgets container).
- 'Legacy unresolvable proxy entry...' — add @vue-nodes tag and assert
  the missing_widget label is not rendered on the host.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 20:17:05 -07:00
GitHub Action
774187e075 [automated] Apply ESLint and Oxfmt fixes 2026-05-13 03:08:05 +00:00