Commit Graph

8020 Commits

Author SHA1 Message Date
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
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
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
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
DrJKL
ed9e984cd2 test(subgraph): use evaluateAll generic instead of HTMLInputElement cast
Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 20:04:21 -07:00
DrJKL
936fd75b69 test(subgraph): drop bespoke RawHostSnapshot interface
Inline the page.evaluate call into getPromotedWidgets so TypeScript infers
the structural shape from the canonical NodeProperty type on
SerializedNode.properties. No separate intermediate type needed.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 20:01:52 -07:00
DrJKL
88129d0e5a test(subgraph): assert .lg-node-widgets is absent for preview-only hosts
Tie the test to the NodeWidgets v-if="nodeData.widgets?.length" gate so a
regression that renders the .lg-node-widgets container for hosts whose only
promoted content is preview exposures will fail.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 19:59:21 -07:00
DrJKL
ce2f76c788 test(subgraph): use canonical guards/schemas for promoted widget snapshots
Refactor PR-review feedback on SubgraphHelper.ts and promotedWidgets.ts:

- Replace inline boolean checks (that masqueraded as type guards) with
  real `is`-predicate functions: `isPromotedWidgetSource`, `isNodeProperty`.
- Drop duplicated `[string, string]` tuple shapes in favor of the existing
  `PromotedWidgetEntry` type alias.
- Move validation outside `page.evaluate` so we can use the canonical
  `parsePreviewExposures` zod schema and the shared `PromotedWidgetSource`
  type from `src/core/...`.
- Eliminate type assertions; narrow via `in` checks and predicate guards.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f3a-a617-70b4-8945-3f0fbd34c1de
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 19:57:09 -07:00
DrJKL
a1484f6ccb fix(subgraph): per-host promoted widget rendering and legacy migration
Two CI Playwright failures in the multi-host primitive fanout test and
the legacy-prefixed proxyWidget normalization test traced to the same
class of mismatch: the migration wrote per-host widget state to a key
the Vue rendering layer didn't read from, and `classify()` accepted
source widgets that `repairCreateSubgraphInput()` then rejected.

Failure 1 — Per-host independence
The migration's `setHostWidgetState` registers values under
`(rootGraphId, hostNodeId, "<name>:<sourceNodeId>:<sourceWidgetName>")`,
but `safeWidgetMapper` produced `storeNodeId/storeName` pointing at the
shared interior key `(rootGraphId, sourceNodeId, sourceWidgetName)`. The
host-scoped value was therefore invisible to Vue; the rendered input
fell back to the empty interior default.

Add `ensureHostWidgetState()` on `PromotedWidgetView` that idempotently
registers a host-scoped store entry seeded with the current effective
value (factored shared registration into `registerHostWidgetState`).
Update `safeWidgetMapper` so promoted widgets call it and expose the
host-scoped `storeNodeId`/`storeName` derived from
`getPromotedWidgetHostStateName`. Two unit tests asserted the previous
interior-keyed identity; updated to assert the host-scoped key while
preserving the same distinct-identity invariant.

Failure 2 — Legacy nested proxy entries
`classify()` resolved source widgets via
`findSourceWidget(...) ?? getPromotableWidgets(...).find(...)`, but
`repairCreateSubgraphInput()` used only `findSourceWidget(...)`. A
legacy entry whose source was a nested SubgraphNode passed
classification then quarantined at repair time, leaving the host with
fewer rendered widgets than expected.

Extract a shared `resolveSourceWidget()` helper applying the same
fallback in both call sites.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f0b-176e-7659-80f5-20323ac54898
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 19:40:42 -07:00
DrJKL
d9209ae44b test(subgraph): use real asset fixtures and @vue-nodes tags
Replace in-test workflow synthesis with saved-workflow assets so the
subgraph serialization tests load fixtures the same way a user would.
Drop the local mutable-workflow types and the manual setting toggles by
adopting the @vue-nodes tag, which auto-enables Vue Nodes and waits
for the rendered surface.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f0b-176e-7659-80f5-20323ac54898
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 19:39:48 -07:00
DrJKL
ff72e5789d fix(subgraph): resolve link-only inputs to host-opaque target (ADR 0009)
resolveSubgraphInputTarget previously gated on getTargetWidget() before
the SubgraphNode-host branch, so an outer SubgraphInput linked to a
nested SubgraphNode whose inner slot has no concrete widget resolved
to undefined. The consumer in useGraphNodeManager then fell back to
the interior PromotedWidgetView's deep (sourceNodeId, sourceWidgetName)
identity, which contradicts ADR 0009's host-scoped opaque contract:
identity is (hostNodeLocator, SubgraphInput.name).

Reorder so the SubgraphNode-host branch runs first and returns
(child.id, targetInput.name) regardless of whether the inner slot has
a backing widget. The widget lookup remains the gate for the
plain-interior-node branch, where a non-widget input legitimately has
no promotable target.

Three existing tests asserted the pre-ADR undefined behavior on
nested-SubgraphNode + non-widget inputs. Update those assertions and
test names to reflect the host-opaque contract; capture the inner
node id from the helper since two helper calls collide on the
hard-coded id.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019e1e84-3270-730a-8c95-49a383ffdf20
2026-05-12 17:01:24 -07:00
DrJKL
4c0df82aee fix(subgraph): address review blockers on promotion + clone hygiene
- proxyWidgetMigration: when a disambiguator is supplied but doesn't
  match, only fall back to non-promoted widgets with the same name so
  we don't silently bind to a sibling PromotedWidgetView.
- previewExposureChain: chainFromLastStep was using the already-advanced
  currentRootGraphId for the leaf, producing an inconsistent
  (rootGraphId, sourceNodeId) pair on cycle / no-exposure / max-depth
  exits. Use the last pushed step's rootGraphId instead.
- promotedWidgetView.applyValueControlToHost: control-after-generate
  was reassigning this.value, which cascades into the shared interior
  widget via the value setter. Switch to hydrateHostValue so the
  per-host overlay is updated without touching shared state.
- promotionUtils: capture promoteValueWidgetViaSubgraphInput results at
  both call sites and emit a warning Sentry breadcrumb on failure
  instead of silently dropping {ok:false, reason}.
- LGraphNode.clone: in UUID mode, configure() was overwriting the
  borrowed source id with the freshly generated UUID before subclass
  hydration could run. Generate the UUID up front and reapply it after
  configure() so hydration sees the borrowed id and the cloned node
  still gets a fresh identity.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1e84-3270-730a-8c95-49a383ffdf20
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 16:49:33 -07:00
DrJKL
ce47bc3771 test(subgraph): assert per-host widget independence via Vue node DOM
Switch the multi-host primitive fanout test to @vue-nodes and assert
against rendered widget inputs. Previous version asserted the same
string-widget value 8 times instead of covering both promoted widgets
per host.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1e14-fdd4-70f4-a3f7-da15caefcd71
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 15:31:59 -07:00
DrJKL
c9bf0eb023 test: replace it.each with it.for to satisfy consistent-each-for
Amp-Thread-ID: https://ampcode.com/threads/T-019e1e22-c4f0-76fb-b57e-e325abb07d6d
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 14:59:14 -07:00
DrJKL
cf2a07098b Merge remote-tracking branch 'origin/main' into drjkl/subgraph-promoted-widget-ratchet-squashed
Amp-Thread-ID: https://ampcode.com/threads/T-019e1e0b-1b64-778a-9cbc-e2675fe6a11b
Co-authored-by: Amp <amp@ampcode.com>

# Conflicts:
#	src/schemas/nodeDefSchema.ts
#	src/stores/nodeOutputStore.ts
2026-05-12 14:22:44 -07:00
DrJKL
92e26d7651 test(subgraph): assert promoted widget values via Vue node DOM
Tag the duplicate-widget-names test with @vue-nodes and use DOM
locators (`getNodeLocator`, `getByRole('textbox')`, `enterSubgraph`)
instead of `page.evaluate`-based graph introspection. The outer
node assertion now checks the rendered textbox value directly, and
the inner node assertion enters the outer subgraph and reads the
two distinguishable text inputs from the inner SubgraphNode in the
DOM.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1db0-3c55-75fe-a542-8bfcbcf87d60
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 14:10:21 -07:00
Dante
9fe19a2afb fix(settings): unify settings item heights and use 14px label text (#12180)
## Summary

Body text in the settings dialog was still rendering at the inherited
16px (browser default) instead of the 14px design spec, and rows with
different control types (toggle, slider, dropdown, radio) collapsed to
different heights — making the list look uneven and cramped.

## Changes

- **What**: `FormItem` label now uses `text-sm` (14px) and the row
enforces `min-h-8` (32px) so toggle/slider/dropdown/radio rows align.
`SettingGroup` bumps inter-item margin from `mb-2` to `mb-3` for
breathing room between settings.

## Review Focus

`FormItem` is also used by `ServerConfigPanel`, so the 14px/32px row
also applies there — consistent with the same settings-dialog visual
language, but worth a glance.

Fixes #FE-525

## Screenshots

Lite Graph panel (1280×900 viewport) showing
toggle/slider/dropdown/radio rows side-by-side:

| Before (`origin/main`) | After (this PR) |
| --- | --- |
| <img
src="https://raw.githubusercontent.com/Comfy-Org/ComfyUI_frontend/pr-12180-screenshots/before-litegraph.png"
width="480"> | <img
src="https://raw.githubusercontent.com/Comfy-Org/ComfyUI_frontend/pr-12180-screenshots/after-litegraph.png"
width="480"> |

Before: label text inherits 16px from `<body>`; toggle-only rows (e.g.
"Always snap to grid", "Live selection") shrink to ~24px while
dropdown/slider rows stay ~32px, so the list looks uneven and cramped.
After: labels are 14px; every row is at least 32px tall so
toggles/sliders/dropdowns/radios line up; `mb-3` adds 4px of breathing
room between rows.

## References

- Linear:
https://linear.app/comfyorg/issue/FE-525/verify-settings-text-size-and-item-heights
- Figma:
https://www.figma.com/design/vALUV83vIdBzEsTJAhQgXq/Comfy-Design-System?node-id=6290-75412
- Origin thread:
https://comfy-organization.slack.com/archives/C075ANWQ8KS/p1777657610484679?thread_ts=1776808927.654249

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
2026-05-12 20:33:05 +00:00
DrJKL
b1139a3b8e fix(subgraph): honor proxyWidgets disambiguator during link migration
When a host SubgraphNode's child has deduplicated promoted widget names
(e.g. `text`, `text_1`), the legacy `proxyWidgets` migration was matching
only by widget name and always picked the first widget. The third tuple
element (source-node id disambiguator, e.g. `["3", "text", "2"]`) was
preserved in the normalized entry but never consulted during resolution,
so outer subgraph nodes ended up exposing the wrong interior widget.

Add `findSourceWidget` that prefers a `PromotedWidgetView` whose interior
identity (`sourceNodeId`, `sourceWidgetName`) matches the disambiguator,
falling back to a name match. Wire it into `classify` and
`repairCreateSubgraphInput`.

Update the existing browser test to actually inspect the outer subgraph
node's exposed widget value (the previous assertion only checked the
inner node, so it passed against the regression). Add a unit test for
the disambiguator path. Re-flip the "deep leaf" SubgraphWidgetPromotion
test to assert resolution via the immediate child's PromotedWidgetView
identity, which is the correct behavior under ADR 0009.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1db0-3c55-75fe-a542-8bfcbcf87d60
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 13:20:25 -07:00
DrJKL
bad2f2c4c8 fix: break promoted widget import cycle
Amp-Thread-ID: https://ampcode.com/threads/T-019e1d98-44cc-7459-bcc5-cc4f0d3b4268
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 12:28:05 -07:00
DrJKL
f8c02e71af test: trim redundant UI tests, restore lost invariants
- SubgraphEditor.test.ts: drop 4 reorder/serialize tests that
  duplicated coverage already in promotionUtils.test.ts and
  SubgraphWidgetPromotion.test.ts; keep the unique preview-exposure
  rendering test.
- useGraphNodeManager.test.ts: add test verifying duplicate-named
  promoted widgets retain distinct storeNodeId identities (ADR 0009).
- WidgetActions.test.ts: add test covering handleHideInput
  per-parent demote branch with sourceNodeId computed from
  node.id===parent.id.
2026-05-12 10:26:45 -07:00
DrJKL
965feb1951 refactor: store and module hygiene pass
- appModeStore: collapse 4 legacy-tuple migration helpers into one
  upgradeAndValidateInput; pruneLinearData becomes a thin map+filter.
- nodeOutputStore: derive *ByExecutionId from the locator index
  instead of mirroring state.
- previewExposureStore: demote unused ResolveNestedHostFn export to
  a local type.
- Inline two trivial wrapper modules (resolvePromotedWidgetSource,
  previewExposureTypes) into their primary consumers.
- previewExposureChain: strip JSDoc, extract leaf builder helper,
  fix double-warn bug on cycle detection.
2026-05-12 10:26:43 -07:00
DrJKL
c2c1621fd2 refactor(LGraph): dispatch trigger events through events target
Adds the four node trigger event payloads (property/slot-errors/
slot-links/slot-label) to LGraphEventMap and makes LGraph.trigger()
dispatch via this.events in addition to calling onTrigger.

Removes the leaky onTrigger monkey-patch in AppModeWidgetList.vue,
replacing it with useEventListener on the events target. The
onTrigger callback path is preserved for back-compat with
useGraphNodeManager (see FE-667 for converting that one too).
2026-05-12 10:26:42 -07:00
DrJKL
a46dee5eed refactor(SubgraphNode): collapse cache, drop sentinel, consolidate serialize tests
- 3-key promoted-view cache replaced with a single version counter;
  exposes invalidatePromotedViews() so promotionUtils can call it at
  the actual mutation site (the missing invalidation was the bug the
  multi-key cache was masking).
- Inline single-use key builders into _getPromotedViews.
- Drop id===-1 sentinel guard in clipboard path by fixing
  construction order: clone path now assigns id before configure().
- SubgraphNode.serialize.test.ts merged into
  SubgraphWidgetPromotion.test.ts as table-driven round-trip
  invariants, replacing spy-on-private assertions.
2026-05-12 10:26:40 -07:00
DrJKL
5fa018ec0c refactor(migration): collapse subsystem to single entry point
Replaces planner/classifier/repair/quarantine helpers and their tests
with a single proxyWidgetMigration module exercised through black-box
round-trip tests. Hook registry indirection replaced with a static
LGraph.proxyWidgetMigrationFlush field assigned in main.ts.

Includes a real semantic fix: classifier now preserves surviving
primitive targets when other targets are dangling.

Net: -16 files, ~-2,300 LoC in src/core/graph/subgraph/migration/.
2026-05-12 10:26:39 -07:00
Comfy Org PR Bot
6845d57a80 chore(website): refresh Ashby roles snapshot (#12191)
Automated refresh of `apps/website/src/data/ashby-roles.snapshot.json`
from the Ashby job board API.

**Flow:**
1. `Release: Website` workflow ran (manual trigger).
2. This PR opens with the regenerated snapshot.
3. `CI: Vercel Website Preview` deploys a preview for review.
4. Merging to `main` triggers the production Vercel deploy.

The snapshot fallback in `apps/website/src/utils/ashby.ts` remains
intact: builds without `WEBSITE_ASHBY_API_KEY` continue to use the
committed snapshot.

Triggered by workflow run `25746888214`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12191-chore-website-refresh-Ashby-roles-snapshot-35e6d73d365081f4b2e1d802dd412a72)
by [Unito](https://www.unito.io)

Co-authored-by: Yourz <8287689+Yourz@users.noreply.github.com>
2026-05-12 16:18:14 +00:00
Christian Byrne
469a5edf99 feat: cloud-nodes catalog at /cloud/supported-nodes (#11903)
*PR Created by the Glary-Bot Agent*

---

## Summary

Adds a comfy.org page that lists every custom-node pack supported on
Comfy Cloud, with per-pack detail subpages. Data is fetched at build
time from `cloud.comfy.org/api/object_info` (gated by
`WEBSITE_CLOUD_API_KEY`), sanitized of user content, joined with public
registry metadata from `api.comfy.org/nodes`, and falls back to a
committed snapshot — mirroring the existing Ashby careers integration
pattern.

- Index: `/cloud/supported-nodes` (en) and
`/zh-CN/cloud/supported-nodes` (zh-CN)
- Detail: `/cloud/supported-nodes/[pack]` and
`/zh-CN/cloud/supported-nodes/[pack]`, generated via `getStaticPaths()`
from the same fetcher as the index so the two routes can never diverge.

## What's new

**Shared package (extracted)**
- `@comfyorg/object-info-parser` — Zod schemas (`zComfyNodeDef`,
`validateComfyNodeDef`), node-source classifier (`getNodeSource`,
`isCustomNode`, `CORE_NODE_MODULES`), and helpers (`groupNodesByPack`,
`sanitizeUserContent`). `src/schemas/nodeDefSchema.ts` and
`src/types/nodeSource.ts` become 1-line re-export shims; existing
imports keep compiling.

**Build-time pipeline**
- `apps/website/src/utils/cloudNodes.ts` — Ashby-style fetcher:
retry/backoff `[1s, 2s, 4s]`, 10 s timeout via AbortController, Zod
envelope + per-node validation, snapshot fallback, memoized via
module-level `inflight` promise.
- `apps/website/src/utils/cloudNodes.registry.ts` — Public registry
enrichment (no auth, batches of 50, single retry, soft-fail).
- `apps/website/src/utils/cloudNodes.ci.ts` — GitHub Actions annotations
+ step summary mirroring the Ashby reporter.
- `apps/website/src/utils/cloudNodes.build.ts` — Single
`loadPacksForBuild()` consumed by both index and detail pages so they
share one source of truth.
- `apps/website/scripts/refresh-cloud-nodes-snapshot.ts` — atomic-rename
refresh CLI that walks pack/node string fields with a user-content
extension regex *before* renaming the snapshot into place.
- Mandatory user-content sanitization strips uploaded filenames from
combo lists (`LoadImage`, `LoadImageMask`, `LoadImageOutput`,
`LoadVideo`, `LoadAudio` zeroed; any combo value matching
`/\.(png|jpe?g|webp|gif|mp4|mov|webm|wav|mp3|flac|ogg|safetensors|ckpt|pt)$/i`
filtered).

**Page + components**
- `apps/website/src/pages/cloud/supported-nodes.astro` (en) + zh-CN
twin.
- `apps/website/src/pages/cloud/supported-nodes/[pack].astro` detail
(en) + zh-CN twin, async `getStaticPaths` driven by
`loadPacksForBuild()`.
-
`apps/website/src/components/cloud-nodes/{HeroSection,PackGridSection,PackCard,PackBanner,NodeList,PackDetail}.vue`
— Vue 3.5 destructured props, `cn()` from `@comfyorg/tailwind-utils`,
design-system tokens only, no PrimeVue.
- Pack card name links to its detail page; banner uses the shared
`fallback-gradient-avatar.svg` asset (copied into
`apps/website/public/assets/images/`) when `banner_url` and `icon` are
missing.
- 25 new `cloudNodes.*` i18n keys in `en` + `zh-CN`.

**Tests**
- 33 unit tests in `@comfyorg/object-info-parser` (schemas, classifier,
sanitizer, grouping).
- 19 new website unit tests covering fetcher (10), CI reporter (6),
registry enrichment (3) — Ashby patterns mirrored.
- E2E: index smoke + search + banner + detail click-through + direct
visit + zh-CN parity.

## Required maintainer follow-up

GitHub Apps cannot push `.github/workflows/*` changes (push was rejected
with `refusing to allow a GitHub App to create or update workflow …
without workflows permission`), so the workflow edits prepared in this
branch were reverted in commit `9be2abce8`. The intended diffs are
documented as copy-paste-ready snippets in `apps/website/README.md`
under the new "Cloud nodes integration → CI wiring" section.

A maintainer must:

1. Provision `WEBSITE_CLOUD_API_KEY` in the repo secrets and the Vercel
project env.
2. Apply the `ci-website-build.yaml` and
`ci-vercel-website-preview.yaml` diffs documented in the README directly
to `main` (or as a follow-up commit on this branch with a maintainer
account).

The committed snapshot lets builds succeed without the secret while the
maintainer step is pending — pages render from
`apps/website/src/data/cloud-nodes.snapshot.json`.

## Self-review (Oracle)

Two warnings caught and fixed in commits `deba5ab02` and `99dfc3381`:
- Index/detail pages now share a single source of truth
(`loadPacksForBuild`), so a fresh fetch can't expose packs whose detail
routes weren't generated.
- Refresh script validates parsed snapshot fields *before* the atomic
rename, instead of regex-scanning the serialized JSON after the file is
already in place.

## Quality gates (local)

```
pnpm --filter @comfyorg/object-info-parser test  →  33 passed
pnpm --filter @comfyorg/website test:unit         →  42 passed
pnpm --filter @comfyorg/website typecheck         →  0 errors
pnpm --filter @comfyorg/website build             →  47 pages built (incl. 6 cloud-nodes routes)
pnpm lint                                         →  0 errors (1 pre-existing warning in unrelated test file)
pnpm knip                                         →  0 errors (1 pre-existing tag hint in unrelated file)
```

E2E (`pnpm --filter @comfyorg/website test:e2e`) is intended to be run
by the Vercel/CI pipelines.

## Manual verification

Built `dist/`, served locally on port 4321, drove with Playwright:
- `/cloud/supported-nodes` renders both pack cards, search input, sort
dropdown
- `/cloud/supported-nodes/comfyui-impact-pack` renders the metadata grid
(publisher, downloads, stars, version, license, last updated) and 3
categorized node sections with 5 nodes total
- `/zh-CN/cloud/supported-nodes` localizes hero (`Comfy Cloud 上的自定义节点`),
label (`云端节点目录`), search placeholder (`搜索节点包或节点名称`), sort
- `/zh-CN/cloud/supported-nodes/comfyui-controlnet-aux` localizes every
metadata label (`查看仓库`, `发布者`, `下载量`, `GitHub 星标`, `最新版本`, `许可证`,
`最后更新`) and renders dates with `Intl.DateTimeFormat('zh-CN')`
(`2026年4月27日`)
- Search input narrows pack count from 2 to 1 when typing `impact`
(verified via DOM count)

Banners render the shared `fallback-gradient-avatar.svg` when the
snapshot's image URL doesn't resolve — expected in the local sandbox.

## Preview URL (after CI completes)

`https://comfy-website-preview-pr-{N}.vercel.app/cloud/supported-nodes`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11903-feat-cloud-nodes-catalog-at-cloud-supported-nodes-3566d73d36508194afdec5f389897585)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-05-12 15:51:24 +00:00
Christian Byrne
35443e94f5 feat(website): SEO model pages — 207 models, FAQ JSON-LD, partner node support (#11892)
## Summary

- Adds programmatic SEO model pages at `/p/supported-models/[slug]` for
**207 models** auto-generated from `workflow_templates` (180 local + 27
partner nodes)
- 3-file architecture: `generated-models.json` (auto-generated, checked
in) + `model-metadata.ts` (editorial overrides) + `models.ts` (65-line
merger)
- Full JSON-LD per page: `SoftwareApplication` + `BreadcrumbList` +
`FAQPage` (targeting AI Overviews / People Also Ask)
- Partner node support: `directory: 'partner_nodes'` hides Download
button, shows VIEW TUTORIAL
- `generate-models.ts`: walks `workflow_templates` for local models +
`API_PROVIDER_MAP` for 30+ partner integrations (Kling, Meshy, Luma,
Runway, Stability AI, ByteDance, Google, etc.)
- Weekly GH Actions workflow opens issue when new models appear in
`workflow_templates` but not in `generated-models.json`
- `add-model-page` Claude skill for Slack-driven model page PRs

## Files changed

| File | Purpose |
|------|---------|
| `apps/website/src/config/generated-models.json` | Auto-generated, 207
models (27 partner + 180 local) |
| `apps/website/src/config/model-metadata.ts` | Editorial overrides:
docsUrl, blogUrl, featured (9 entries) |
| `apps/website/src/config/models.ts` | 65-line merger — imports JSON +
overrides, exports `models` + `getModelBySlug` |
| `apps/website/scripts/generate-models.ts` | Build-time parser; run
with `pnpm generate:models` |
| `apps/website/src/i18n/translations.ts` | ~30 UI keys added (no
per-model keys — displayName is plain string) |
| `apps/website/src/pages/p/supported-models/[slug].astro` | Dynamic
route with 3x JSON-LD schemas |
| `apps/website/src/pages/p/supported-models/index.astro` | Model grid
index page |
| `apps/website/src/components/models/ModelHeroSection.vue` | Hero
component |
| `.github/workflows/model-page-discovery.yaml` | Weekly auto-discovery
workflow |
| `.claude/skills/add-model-page/SKILL.md` | Claude skill for
adding/updating model pages |

## Test plan

- [ ] `pnpm build` passes in `apps/website`
- [ ] `/p/supported-models` index renders 207 model cards
- [ ] `/p/supported-models/kling-ai` shows Partner Node eyebrow, no
Download button, VIEW TUTORIAL CTA
- [ ] `/p/supported-models/flux-1-dev` shows Diffusion Model eyebrow,
Download + Tutorial buttons
- [ ] `/p/supported-models/umt5-xxl-fp8-e4m3fn-scaled` redirects 301 to
`umt5-xxl-fp16` (canonicalSlug)
- [ ] Structured data validator shows FAQPage + SoftwareApplication +
BreadcrumbList valid

Fixes FE-421

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:50:23 +00:00
Dante
e05e6cd2fb test: FE-230 e2e regression for asset-delete clearing Load Image preview (#12131)
## Summary

Follow-up to #11493 — adds a `@cloud` Playwright spec that drives the
asset-deletion flow end-to-end and asserts the four FE-230 outcomes
wired up in `useMediaAssetActions.deleteAssets`.

- Path: `browser_tests/tests/assetDeleteClearsLoadImage.spec.ts`
- Flow: load `widgets/load_image_widget`, seed `widget.value` +
`node.imgs`, reset changeTracker, open assets sidebar → Imported tab →
right-click card → Delete → confirm dialog.
- Asserts (auto-retrying): DELETE `/assets/:id` request issued,
`widget.value === ''`, `node.imgs.length === 0`, `workflow.isModified
=== true`.
- Tagged `@cloud` because input-asset deletion is gated on `isCloud` in
`deleteAssetApi`.

Addresses [this review
thread](https://github.com/Comfy-Org/ComfyUI_frontend/pull/11493#pullrequestreview-4262129237)
from #11493.

## Test plan

- [ ] `pnpm build:cloud && pnpm exec playwright test --project=cloud
browser_tests/tests/assetDeleteClearsLoadImage.spec.ts --reporter=list`
passes locally with a running ComfyUI backend
- [ ] CI `cloud` matrix passes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12131-test-FE-230-e2e-regression-for-asset-delete-clearing-Load-Image-preview-35d6d73d36508192b4a2df7860f48c44)
by [Unito](https://www.unito.io)
2026-05-12 14:42:31 +00:00
pythongosssss
681915275e fix: remove failed to export toast when cancelling export workflow (#12134)
## Summary

An incorrect error toast currently shows when cancelling the workflow
export from an asset

## Changes

- **What**: 
- skip toast on cancel
- add e2e & unit tests
- refactor asset tab open helper to wait by default & cleanup usage

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12134-fix-remove-failed-to-export-toast-when-cancelling-export-workflow-35d6d73d3650815b839ff26edd70a472)
by [Unito](https://www.unito.io)
2026-05-12 09:00:46 +00:00
pythongosssss
3d9c9ce327 fix: update color widget colors (#12133)
## Summary

The widget was using a different fg/background compared to all other
widgets, this updates to use the standard classes.

## Changes

- **What**: 
- update styles

## Screenshots (if applicable)

Before
<img width="570" height="597" alt="Screen Shot 2026-05-11 at 07 19 12"
src="https://github.com/user-attachments/assets/18a5330f-5e9a-4d16-b3f0-0acfab5d6f99"
/>

After
<img width="570" height="597" alt="Screen Shot 2026-05-11 at 07 15 46"
src="https://github.com/user-attachments/assets/81c9da58-fdda-4539-ae1e-98727f12b9ac"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12133-fix-update-color-widget-colors-35d6d73d365081da8515cd48a8e8ecc2)
by [Unito](https://www.unito.io)

No e2e tests for this as they would be simple screenshot asserts on the
color of nodes, which is not worthwhile.
2026-05-12 09:00:22 +00:00
jaeone94
e765eb1bb2 fix: suppress missing media scan during uploads (#12111)
## Summary
- Prevent missing media detection from scanning media loader nodes while
a drag/drop, paste, or file-select upload is still in progress.
- Align LoadAudio with the existing media upload lifecycle by setting
`node.isUploading`, blocking concurrent uploads, and clearing the flag
after upload completion.
- Keep added-node model and missing-node scans on the original
one-microtask path, while deferring added-node media scanning by one
extra microtask so upload handlers can mark transient upload state
before the scan reads widget values.

## Why
Drag/drop and paste can create media loader nodes before the backing
upload has settled. During that short window, the widget may contain a
local filename that is not yet backend-resolvable, so missing media
detection can surface a false missing asset. Refreshing works because
the upload has completed by then.

## Follow-up
- E2E coverage for this upload race will be handled in a follow-up PR
together with E2E coverage for the annotated output-media path changes
from #12069.

## Validation
- `pnpm format`
- `pnpm lint`
- `pnpm typecheck`
- `pnpm vitest run src/composables/graph/useErrorClearingHooks.test.ts
src/platform/missingMedia/missingMediaScan.test.ts
src/extensions/core/uploadAudio.test.ts
src/composables/node/useNodeImageUpload.test.ts`
- Re-ran `pnpm typecheck` after rebasing onto latest `main`
- Pre-push `knip` hook passed

Fixes FE-620

## Screenshots 

Before


https://github.com/user-attachments/assets/db7891de-a4b5-4cde-aa76-6340e6cdf7b2

After


https://github.com/user-attachments/assets/9b99bb13-0d5b-4ff7-8f52-66eea6e417ec



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12111-fix-suppress-missing-media-scan-during-uploads-35b6d73d365081f3b54eed02874ccaa4)
by [Unito](https://www.unito.io)
2026-05-12 08:54:11 +00:00
Rizumu Ayaka
56434ae9ac perf: debounce template search input to keep typing responsive (#12183)
## Summary

Route the templates dialog search through `FormSearchInput`'s debounced
searcher so per-keystroke work no longer trips the heavy filter/render
path.

## Changes

- **What**: `WorkflowTemplateSelectorDialog` now uses `FormSearchInput`
instead of `SearchInput`. The raw input is bound to a local ref; the
actual `searchQuery` consumed by `useTemplateFiltering` is only written
after the input debounce settles, so dependent computeds (notably
`shouldUsePagination`, which used to flip on every keystroke and force a
full grid rebuild) stay stable while typing.
- **What**: `FormSearchInput` gains optional `debounceMs` (default
`250`) and `debounceMaxWaitMs` (default `1000`) props. Existing callers
are unchanged; the templates dialog passes `400` / `4000` to match the
feel tuned in this PR.

## Review Focus

- Reset path: `searchQuery` is still owned by `useTemplateFiltering` and
cleared by `resetFilters`; a watch syncs the visible input back to empty
when that happens.
- `FormSearchInput` is currently under `src/renderer/...` but already
imported by workbench-level components (rightSidePanel tabs). This PR
follows that existing precedent rather than relocating the component.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12183-perf-debounce-template-search-input-to-keep-typing-responsive-35e6d73d365081b7a11ec4a84323095f)
by [Unito](https://www.unito.io)
2026-05-12 08:15:31 +00:00
Alexander Brown
25c2d828c0 test: enable vitest/consistent-each-for and migrate .each → .for (#12161)
*PR Created by the Glary-Bot Agent*

---

Enables the oxlint rule `vitest/consistent-each-for` (configured to
prefer `.for` for `test`, `it`, `describe`, and `suite`) and migrates
every `.each` parameterized test in the repo to `.for`. Using `.for`
avoids accidentally splatting tuple elements into separate callback
arguments and exposes `TestContext` as the second callback argument.

The first commit covers the 38 lint-detected files (88 callsites):
renames `.each` → `.for` and updates callback signatures to destructure
when the data is an array of tuples (objects/primitives already work
unchanged with `.for`).

The follow-up commit addresses code review feedback: oxlint's rule does
not recognize `test.each` on extended test bases
(`baseTest.extend(...)`) and skips files in `ignorePatterns`
(`src/extensions/core/*`). These were converted manually so the policy
is uniform across the codebase.

## Verification

- `node_modules/.bin/oxlint src` — 0 errors, 0 `consistent-each-for`
violations
- `pnpm typecheck` — passes
- `pnpm test:unit` — all modified test files pass; pre-existing
environmental flakes (`GraphView.test.ts`, `ColorWidget.test.ts`, etc.,
unchanged here and flaky on `main` in this sandbox) are unrelated
- `pnpm lint` / `pnpm knip` — clean
- Manual verification: 362 tests across 6 representative converted
suites re-run in an interactive shell — all passing

Manual UI verification (Playwright/screenshots) is not applicable:
changes are test-file-only refactors with no production runtime or UI
behavior change.

## Notes on `.for` semantics

- Array-of-tuples (`[[a, b], ...]`) passes the tuple as a single arg, so
callbacks were changed from `(a, b) => …` to `([a, b]) => …`.
- Array-of-objects (`[{a}, …]`) already used destructuring — unchanged.
- Array-of-primitives (`['a', …]`) — callback signature unchanged.
- A handful of complex cases use a small `type Case = [...]` alias plus
`it.for<Case>([...])` to preserve tuple inference where TS narrowed
unions otherwise broke parameter types.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12161-test-enable-vitest-consistent-each-for-and-migrate-each-for-35e6d73d3650810c9417e07bdd9f27a2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-11 23:11:51 -07:00
Dante
ceb9936058 fix(i18n): clamp unsupported browser locales to a shipped tag (#11712)
## Summary

Sidebar buttons rendered literal i18n keys (e.g.
`sideToolbar.labels.assets`) on a fresh install when the user's
`navigator.language` base tag wasn't one of the 12 shipped locales —
German/Italian/Polish/Dutch/Brazilian-Portuguese users among others.

## Changes

- **What**: Add `resolveSupportedLocale()` that tries the full BCP-47
tag first (preserves `zh-TW`, `pt-BR`), then the base tag, then `'en'`.
Wire through both entry points (`createI18n`'s initial locale,
`Comfy.Locale`'s `defaultValue`) and clamp inside `loadLocale`,
propagating the resolved tag to `GraphView` so a stale stored
`Comfy.Locale='de'` from older builds also recovers.
- **Side benefit**: Brazilian Portuguese users were previously falling
through `pt-BR` → `pt` (unshipped) → broken. The full-tag-first lookup
now correctly lands them on the `pt-BR` bundle.
- **Breaking**: None.
- **Dependencies**: None.

## Root Cause

Three-link chain:

1. `Comfy.Locale`'s default was `() => navigator.language.split('-')[0]
|| 'en'`. German → `'de'` (unshipped).
2. `loadLocale('de')` silently `console.warn`'d and returned without
throwing.
3. `GraphView` then ran `i18n.global.locale.value = 'de'` anyway.
4. `st(key, fallback) = te(key) ? t(key) : fallback`. vue-i18n's `te()`
checks **only** the current locale and ignores `fallbackLocale` — every
key missed → `st()` returned the literal key string.

Two pathways reached the broken state (defaultValue path, and
unset-setting path through `createI18n`'s own `navigator.language`
snapshot); the new helper closes both.

## Review Focus

- `loadLocale` now returns `SupportedLocale` (was `void`). Old `void`
callers continue to compile; the only change is `GraphView` consuming
the return value.
- Unit-tested in `src/i18n.test.ts` (added `resolveSupportedLocale`
block + updated the `loadLocale` unsupported-locale case from "warn" to
"clamp to en").
- Self-reproduced via Playwright with `navigator.language='de-DE'` +
fresh-install state on both `main` (shows the bug) and this branch
(shows the fix). Spec saved at
`temp/scripts/issue-10563-locale-bug.spec.ts`.

Fixes #10563
FE-480 — https://linear.app/comfyorg/issue/FE-480

## Screenshots

**Before** (from #10563, on `main`):

<img width="258" height="399" alt="Sidebar with literal i18n keys"
src="https://github.com/user-attachments/assets/098d1d76-8e89-4237-813f-5f030b34e51e"
/>

**After** (this branch, same `navigator.language='de-DE'`):

<img width="367" height="793" alt="Screenshot 2026-04-28 at 2 07 38 PM"
src="https://github.com/user-attachments/assets/9d279de3-50a8-4774-999f-ab4c3018a9ef"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11712-fix-i18n-clamp-unsupported-browser-locales-to-a-shipped-tag-3506d73d3650812f89d2f0fe3199de3a)
by [Unito](https://www.unito.io)
2026-05-12 15:06:13 +09:00
Deep Mehta
bb420fe2c7 feat: add model-to-node mappings for new model directories (#12151)
## Summary

Add entries to `MODEL_NODE_MAPPINGS` so the model browser's "Use" button
correctly creates a loader node for five model directories that
currently have no mapping.

## Changes

- **What**: 5 new entries in
`src/platform/assets/mappings/modelNodeMappings.ts`:
- `background_removal` → `LoadBackgroundRemovalModel` /
`bg_removal_name` (ComfyUI v0.21+ core)
- `frame_interpolation` → `FrameInterpolationModelLoader` / `model_name`
(ComfyUI v0.21+ core)
  - `film` → `FILM VFI` / `ckpt_name` (ComfyUI-Frame-Interpolation)
- `ultralytics/bbox` → `UltralyticsDetectorProvider` / `model_name`
(ComfyUI-Impact-Pack)
- `ultralytics/segm` → `UltralyticsDetectorProvider` / `model_name`
(ComfyUI-Impact-Pack)
- **Breaking**: none

## Review Focus

- Node class names and input keys were cross-checked against the ComfyUI
v0.21.0 source and the published Impact-Pack / Frame-Interpolation node
definitions
- Both `ultralytics/bbox` and `ultralytics/segm` map to the same node
(`UltralyticsDetectorProvider`); its `model_name` combo accepts values
from both subdirectories (`bbox/...` and `segm/...`)
- `film` and `frame_interpolation` are separate directories serving
different node packs — keeping them as distinct entries rather than
collapsing under a parent

## Test plan

- [ ] In the model browser, clicking "Use" on `birefnet.safetensors`
creates a `LoadBackgroundRemovalModel` node with the model preselected
- [ ] Same for one model in each of: `frame_interpolation`, `film`,
`ultralytics/bbox`, `ultralytics/segm`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12151-feat-add-model-to-node-mappings-for-new-model-directories-35d6d73d365081ff834bf6eb610da160)
by [Unito](https://www.unito.io)
2026-05-12 02:28:51 +00:00
pythongosssss
4504256f11 test: add test for custom node i18n (#12132)
## Summary

Adds e2e test for custom node i18n

## Changes

- **What**: 
- add e2e regression for previous fix with no e2e

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12132-test-add-test-for-custom-node-i18n-35d6d73d365081f7bed2db39af38f855)
by [Unito](https://www.unito.io)
2026-05-12 01:04:38 +00:00
Comfy Org PR Bot
1290bbd359 1.45.5 (#12152)
Patch version increment to 1.45.5

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12152-1-45-5-35e6d73d365081c39aecddf42d8f7a2a)
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.5
2026-05-12 00:44:22 +00:00
Yourz
7ddf71d91b fix(website): center GitHubStarBadge text in Safari (#12138)
*PR Created by the Glary-Bot Agent*

---

## Summary

The 10px star count text inside the desktop nav GitHub star badge
rendered vertically off-center in Safari/WebKit. The text was visibly
shifted upward inside the yellow badge body, while Chromium rendered it
centered correctly.

## Root cause

`NodeBadge.vue` centers its inner text span by setting `flex
items-center justify-center` on the segment, then nudging the text with
`translate-y-1` (or `translate-y-0` for the small variant). The text
span itself is an `inline-block` with no explicit `line-height`, so it
inherits the default `line-height: 1.5` (15px for a 10px font).

Safari and Chromium distribute that extra leading differently for the
`PP Formula Narrow` custom font: Safari pushes the glyph higher inside
its 15px line box, while Chromium positions it near the middle. With
only a 5px gap above/below the glyph to play with, that browser-specific
divergence is enough to make the badge look misaligned in Safari.

## Fix

Add `leading-none` to the star count text class so the inline-block's
line box equals the font size (10px) and the parent flex container's
`items-center` produces deterministic vertical centering across
browsers.

Verified at lg breakpoint (1440×900) in both WebKit and Chromium via
Playwright; the badge now renders identically and is properly centered.

## Verification

- `pnpm typecheck` (website) — clean
- `pnpm build` (website) — 51 pages built successfully
- Pre-commit hooks (stylelint, oxfmt, oxlint, eslint, typecheck,
typecheck:website) — all passed
- Visual inspection in WebKit and Chromium via Playwright

Fixes FE-648

## Screenshots

![BEFORE (Safari/WebKit): star count text displaced upward in yellow
badge](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/364b285ccc004b7d3778b6129dab4410e0bffe9db5ef124e37443414f9790d70/pr-images/1778513564903-c047c9ac-4719-479e-8442-f15ad389be02.png)

![AFTER (Safari/WebKit): star count text properly centered in yellow
badge](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/364b285ccc004b7d3778b6129dab4410e0bffe9db5ef124e37443414f9790d70/pr-images/1778513565238-a4f00af8-54d7-4131-a5fd-849105e427d6.png)

![Zoomed Safari/WebKit view showing the fixed badge with centered 85K
text](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/364b285ccc004b7d3778b6129dab4410e0bffe9db5ef124e37443414f9790d70/pr-images/1778513565587-f6aca193-5ced-45aa-b692-3340629f64be.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12138-fix-website-center-GitHubStarBadge-text-in-Safari-35d6d73d3650818aa0e8e0f341b60378)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-12 00:38:49 +00:00
Christian Byrne
74caeb0b0b fix: detect V1/V2 draft storage keys in new-user check (#11728)
## Problem

`checkIsNewUser()` in `useNewUserService` only checked legacy pre-V1
localStorage keys (`workflow`, `Comfy.PreviousWorkflow`) to determine if
a user had prior workflow history. A returning user who had only ever
used the V1 or V2 draft persistence system would have neither of those
keys set, causing `isNewUser()` to return `true` and the getting-started
tab to appear in the workflow templates dialog after a settings reset.

## Solution

Extend the check to also cover:
- **V1 draft store keys**: `Comfy.Workflow.Drafts`,
`Comfy.Workflow.DraftOrder`
- **V2 draft index key**: `Comfy.Workflow.DraftIndex.v2:personal`

The `personal` scope is hardcoded for the V2 check because at the time
`checkIsNewUser()` runs, the cloud workspace ID (stored in
sessionStorage) may not be set yet. This is fine — any genuine new user
will have no personal workspace index regardless.

The original legacy keys are preserved for users who may still have them
from older installs.

## Tests

Added three new test cases covering V1 draft store keys, V1 draft order
key, and V2 draft index key.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11728-fix-detect-V1-V2-draft-storage-keys-in-new-user-check-3506d73d3650819ca4cfc8e83d95c258)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Connor Byrne <c.byrne@comfy.org>
2026-05-11 20:16:04 +00:00
Alexander Brown
ced7c93e63 testing: Improve custom checks in .coderabbit.yaml (#12141)
Update coderabbit end-to-end check logic.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12141-testing-Improve-custom-checks-in-coderabbit-yaml-35d6d73d3650818e8be2f0b7d403683b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-11 19:05:48 +00:00
Christian Byrne
759ed3d4e2 feat(website): add community-workflows demo page (#11942)
*PR Created by the Glary-Bot Agent*

---

Adds a new interactive demo page at
`comfy.org/demos/community-workflows` for the [Explore and Use a
Community Workflow from the
Hub](https://app.arcade.software/flows/mqZh17oWDuWIyhK0xwEV/view) Arcade
walkthrough.

Built on top of the demo infrastructure merged in #11436.

## Changes

- `apps/website/src/config/demos.ts` — register the new demo
- `apps/website/src/i18n/translations.ts` — add en + zh-CN strings
(title, description, transcript)
- `apps/website/public/images/demos/community-workflows-og.png` —
1200×630 OG image so email/social previews render correctly
- `apps/website/public/images/demos/community-workflows-thumb.webp` —
1280×720 WebP thumbnail
- `apps/website/e2e/demos.spec.ts` — refactored to iterate `demos` from
config so every demo (current + future) is exercised in both en and
zh-CN, and the iframe `src` is asserted to contain the correct Arcade ID

Adding a new demo only requires editing `demos.ts` + `translations.ts`
going forward; the e2e refactor is a one-time generalization that gives
future demos coverage automatically.

## Verification

- `pnpm typecheck:website`: 0 errors, 0 warnings, 0 hints
- Pre-commit hook ran `pnpm typecheck`, `oxfmt`, `oxlint`, `eslint` —
all clean on staged files
- `npx astro build`: 53 pages built; `/demos/community-workflows/` and
`/zh-CN/demos/community-workflows/` generated and present in
`sitemap-0.xml`
- Page rendered in Playwright preview: hero (title, GETTING STARTED,
BEGINNER, ~2 min), Arcade embed loads, transcript section present,
"What's Next" links to `image-to-video`
- zh-CN page shows localized title (探索并使用社区工作流), description, badges,
and "What's Next" heading
- OG meta tag references the new 1200×630 PNG

## Screenshots

![English demo page at /demos/community-workflows showing hero, embedded
Arcade walkthrough, transcript section, and What's Next
navigation](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/9e9c345d23c5dd7674edc13e935c656681feb2a09b0928339594232502fa74db/pr-images/1777945965156-23d92854-3b6a-4470-92fb-86facb28a915.png)

![Chinese localized demo page at /zh-CN/demos/community-workflows with
translated title, category, badges, and
navigation](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/9e9c345d23c5dd7674edc13e935c656681feb2a09b0928339594232502fa74db/pr-images/1777945965557-f981dfb3-4080-41e5-b93c-30801b4e1e0c.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11942-feat-website-add-community-workflows-demo-page-3576d73d36508139b647c774b1d39323)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-11 12:21:58 -07:00
pythongosssss
5d53e75d23 test: add tests for deprecated & api node badge visibility settings (#11681)
## Summary

Adds coverage for untested node badges

## Changes

- **What**: 
- add shared addNode helper - will follow up to standardize across tests
- add deprecated & api node badge tests in LiteGraph & Vue nodes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11681-test-add-tests-for-deprecated-api-node-badge-visibility-settings-34f6d73d365081569129ecffa608122e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-05-11 18:51:52 +00:00
Comfy Org PR Bot
d23e86d9a4 [chore] Update Ingest API types from cloud@0a03f3a (#12043)
## Automated Ingest API Type Update

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

- Cloud commit: 0a03f3a
- 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-11 18:47:18 +00:00
Yourz
d901c63a0b feat: convert careers CategoryNav to scroll-spy locator (#12110)
*PR Created by the Glary-Bot Agent*

---

Converts the `CategoryNav` in the careers `RolesSection` from a
click-to-filter button into a scroll-spy section locator, matching the
pattern already used by `ContentSection.vue` (customer story details,
TOS, privacy policy).

## Changes

- **`apps/website/src/components/careers/RolesSection.vue`**
- Replaced category-based filtering with anchor navigation: clicking a
department in the sidebar smooth-scrolls (via existing Lenis/GSAP
`scrollTo` helper) to that department's section with a `-144px` header
offset.
- Removed the `ALL` button — every department is always rendered as its
own scroll target with `id="careers-dept-{key}"`.
- Added `useIntersectionObserver` (rootMargin `-20% 0px -60% 0px`) that
updates the active nav item as the user scrolls. An `isScrolling` guard
prevents the observer from fighting click-jumps mid-animation.
- Added a viewport-entry fade/slide-up animation on each department
section, gated by `motion-safe:` so users with `prefers-reduced-motion`
see content immediately. The reveal state is sticky (one-way) so
sections don't disappear once revealed.
- Active state is driven by raw department keys; both the nav model and
the observer's id-to-key mapping use a single consistent identifier.

- **`apps/website/e2e/careers.spec.ts`**
- Replaced the obsolete "ENGINEERING filter narrows the list" test with
one that validates locator behavior: clicking the department button
scrolls the section into the viewport, sets `aria-pressed="true"`, and
keeps the full role list rendered.

## Verification

- `pnpm --filter @comfyorg/website typecheck` — clean.
- `pnpm exec oxfmt` / `pnpm exec eslint` / `pnpm exec oxlint` — clean.
- Pre-commit lint-staged hooks (stylelint, oxfmt, oxlint, eslint,
typecheck) — passing.
- Manual smoke test via Playwright on `astro dev`: careers page renders
all departments stacked vertically, active department in the sidebar
highlights based on viewport position (DESIGN active on initial scroll),
nav items reflect each department instead of including an `ALL` button.

## Screenshots

![Careers page with new locator: sidebar shows all 4 departments as
anchor buttons (DESIGN highlighted as active), all roles rendered
without
filtering](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/7f5c6359393f3f1f03c628d10074b13924f36c44bbce73e6a2729dd8279d9304/pr-images/1778336774660-dae22a86-d4f3-4509-bc66-eb3d00ec308c.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12110-feat-convert-careers-CategoryNav-to-scroll-spy-locator-35b6d73d3650818a9226e5dcb1244756)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary <glary@bot.local>
2026-05-11 18:42:25 +00:00