mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-21 21:09:00 +00:00
bdb92c845e846d97a00e4ea1bec2d8fb9d2bbb22
8016 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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` ( |
||
|
|
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> |
||
|
|
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 |
||
|
|
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) |
||
|
|
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) |
||
|
|
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 |
||
|
|
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> |
||
|
|
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) |
||
|
|
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) |
||
|
|
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> |
||
|
|
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) |
||
|
|
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) |
||
|
|
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) |
||
|
|
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) |
||
|
|
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  ┆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> |
||
|
|
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) |
||
|
|
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) |
||
|
|
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  ┆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> |
||
|
|
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> |
||
|
|
a0150ffe17 |
1.45.6 (#12204)
Patch version increment to 1.45.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12204-1-45-6-35f6d73d365081fc8539ca25a55aac74) 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.6 |
||
|
|
c92030b158 |
refactor: deduplicate Civitai hostname logic in getSourceName (#11822)
## Summary
- Extract `isCivitaiHost` private helper from `isCivitaiModelUrl` in
`formatUtil.ts` for DRY hostname checking
- Add `isCivitaiUrl` exported function for hostname-only Civitai URL
detection (distinct from `isCivitaiModelUrl` which also validates the
path format)
- Refactor `getSourceName` in `assetMetadataUtils.ts` to use the shared
`isCivitaiUrl` instead of inline duplicate hostname checks
- Add tests for `isCivitaiUrl` covering `.com`, `.red`, subdomain, and
invalid URL cases
## Changes
- `packages/shared-frontend-utils/src/formatUtil.ts` — add
`isCivitaiHost` private helper + export `isCivitaiUrl`; refactor
`isCivitaiModelUrl` to use helper
- `packages/shared-frontend-utils/src/formatUtil.test.ts` — add
`isCivitaiUrl` test suite
- `src/platform/assets/utils/assetMetadataUtils.ts` — import
`isCivitaiUrl` from `@/utils/formatUtil`; remove inline hostname logic
from `getSourceName`
## Testing
### Automated
- Added `isCivitaiUrl` test suite (6 cases: `.com`, `.red`, subdomains,
non-Civitai, invalid URL)
- All 71 existing `formatUtil` tests pass
- All 53 existing `assetMetadataUtils` tests pass (behavior preserved)
- TypeScript typecheck passes
### E2E Verification Steps
1. Run unit tests: `npx vitest run
packages/shared-frontend-utils/src/formatUtil.test.ts
src/platform/assets/utils/assetMetadataUtils.test.ts`
2. Expected: all tests pass
3. Verify `getSourceName('https://civitai.red/models/123')` returns
`'Civitai'`
4. Verify `isCivitaiUrl('https://civitai.com/models/any-path')` returns
`true`
5. Verify `isCivitaiModelUrl` still rejects non-API paths while
`isCivitaiUrl` accepts them
## Review Focus
`isCivitaiUrl` (new, hostname-only) vs `isCivitaiModelUrl` (existing,
hostname+path format): `getSourceName` needs to recognize ANY Civitai
URL as a source, so using `isCivitaiModelUrl` directly would incorrectly
reject valid browse URLs like `civitai.com/models/123`.
Closes #11357
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11822-refactor-deduplicate-Civitai-hostname-logic-in-getSourceName-3546d73d36508110974ccc3b7384d82b)
by [Unito](https://www.unito.io)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
988d532467 |
fix(queue): contain JobDetailsPopover error message overflow (#12173)
## Summary Cap the Job Details "Error message" block at `max-h-96` (24rem / 384px) with an internal scroll, wrap long unbreakable tokens (filenames, JSON), and preserve newlines so the failed-job popover no longer grows unbounded. ## Changes - **What**: Added `max-h-96 overflow-y-auto whitespace-pre-wrap wrap-break-word` to the error message container in `JobDetailsPopover.vue`, plus a `FailedWithLongError` Storybook story covering the overflow case. ## Review Focus - 24rem cap was set per Alex's spec in the [Slack thread](https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1778506109115989). - `wrap-break-word` (Tailwind 4 canonical of `break-words`) is needed because long underscore-joined filenames don't break naturally; `whitespace-pre-wrap` preserves any newlines in the raw error. - Not in scope: the popover z-index clipping issue Alex flagged later in the same thread — that's a separate follow-up. Fixes FE-660 ## Screenshots **Before** — error block grows unbounded with the panel:  **After** — error block capped at 384px and internally scrollable:  Reproduce locally via Storybook: `pnpm storybook` → Queue → JobDetailsPopover → **FailedWithLongError**. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12173-fix-queue-contain-JobDetailsPopover-error-message-overflow-35e6d73d3650812d9873e5d163cad0c6) by [Unito](https://www.unito.io) --------- Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> |
||
|
|
fe08ad2fcd |
Fix pre-commit linter skipping type checks (#12203)
Adds the `--type-aware` so that the typechecks performed by precommit
hooks have parity with the results output by a full `pnpm lint`
Most notably, unawaited promises would not be caught by the precommit
hooks prior to this PR.
```
× typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
╭─[browser_tests/fixtures/utils/vueNodeFixtures.ts:45:5]
44 │ async select() {
45 │ this.header.click()
· ───────────────────
46 │ }
╰────
```
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12203-Fix-pre-commit-linter-skipping-type-checks-35e6d73d365081a4adade833294df7ed)
by [Unito](https://www.unito.io)
|
||
|
|
93edf166d0 |
fix(website): link careers page to Ashby job description, not application form (#12200)
*PR Created by the Glary-Bot Agent*
---
## Summary
The careers page at comfy.org/careers was linking every role to its
Ashby application form (`.../{id}/application`) instead of the job
description page (`.../{id}/`). Users expect to first read the role
description, not land on the submit-resume page.
Ashby's job board API returns both `jobUrl` (description) and `applyUrl`
(application form). `toDomainRole` was preferring `applyUrl`; this PR
switches to `jobUrl` and renames the `Role` field accordingly so the
field name matches its meaning.
## Changes
- `apps/website/src/utils/ashby.ts`: use `job.jobUrl` directly instead
of `job.applyUrl ?? job.jobUrl`.
- `apps/website/src/data/roles.ts`: rename `Role.applyUrl` →
`Role.jobUrl`.
- `apps/website/src/components/careers/RolesSection.vue`: update the `<a
:href>` binding.
- `apps/website/src/data/ashby-roles.snapshot.json`: regenerated
fallback snapshot — URLs stripped of `/application`, `id`s recomputed
from the new URLs.
- Unit + E2E tests updated; new E2E assertion that links do not end in
`/application` prevents regressions.
The Ashby schema (`ashby.schema.ts`) still accepts `applyUrl` since the
API returns it — we just no longer consume it.
## Verification
- `pnpm test:unit` — 70/70 pass
- `pnpm typecheck` — 0 errors
- `pnpm build` — succeeds; inspected `dist/careers/index.html`, all 19
Ashby links now point to description URLs and zero contain
`/application`
- Oracle code review — 0 issues
Fixes user report in #hiring-ideas (Slack).
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12200-fix-website-link-careers-page-to-Ashby-job-description-not-application-form-35e6d73d3650815cbedadf974f7d3364)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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) |
||
|
|
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) |
||
|
|
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. |
||
|
|
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) |
||
|
|
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) |
||
|
|
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>
|
||
|
|
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)
|
||
|
|
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) |
||
|
|
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) |
||
|
|
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 |
||
|
|
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    ┆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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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   ┆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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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

┆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>
|
||
|
|
5ca9f3e7e6 |
feat(website): remove left-edge fade-out from local hero illustration (#12137)
*PR Created by the Glary-Bot Agent* --- Removes the left-edge fade-out gradient overlay on the hexagonal hero illustration on the `/download` (local) page. The hex cluster now reads fully edge-to-edge instead of being blended into the page background on the left side. Tracked in [FE-650: Remove fade-out effect on local page hero illustration](https://linear.app/comfyorg/issue/FE-650/remove-fade-out-effect-on-local-page-hero-illustration). ## Change Drops the `<!-- Left-edge fade -->` `<rect>` and its `<defs><linearGradient id="localHeroFadeLeft">…</linearGradient></defs>` from `apps/website/src/components/product/local/HeroSection.vue`. Animation logic (panel expansion + hex ring rotation) is untouched. ## Verification - `pnpm nx typecheck website` — pass (0 errors) - `pnpm nx build website` — pass (51 pages built) - `pnpm exec eslint apps/website/src/components/product/local/HeroSection.vue` — clean - `pnpm format:check apps/website/src/components/product/local/HeroSection.vue` — clean - Manual: `pnpm dev` + visited `/download` at 390×844 (mobile), 1280×800, and 1440×900. Mobile screenshots clearly show the fade is gone; the leftmost hexagons are now fully visible. ## Screenshots Mobile (390×844), before — note the dark fade obscuring the left side of the hex cluster: ## Screenshots    ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12137-feat-website-remove-left-edge-fade-out-from-local-hero-illustration-35d6d73d365081cba690ed7d46a19882) by [Unito](https://www.unito.io) Co-authored-by: Glary Bot <bot@glary.dev> |
||
|
|
6d5fa743b3 |
fix: seamless SocialProofBar marquee loop (#12139)
*PR Created by the Glary-Bot Agent* --- ## Summary The partner-logo marquee on the homepage `SocialProofBar` glitched on every loop restart — a visible jump where the strip snapped back to the start. ## Root cause The previous implementation rendered all logos as siblings of a single flex container and animated the track from `translateX(0)` to `translateX(-50%)`. Because the `gap` utility inserts spacing between every adjacent pair of items (including the seam between the two duplicated halves), `-50%` of the total width does not equal the distance from one half-start to the next half-start. The mismatch (`gap / 2`) is exactly what the eye sees as a jump. ## Fix - Wrap each duplicated half in its own flex group. - Place the two groups as siblings of an outer `flex w-max gap-X` track with a gap that matches the inner gap. - Animate each group by `translateX(calc(-100% - var(--marquee-gap)))`, where `--marquee-gap` is set inline to the same value as the Tailwind gap class. - Scope the `animation` declaration to `@media (prefers-reduced-motion: no-preference)` so reduced-motion users get a stable, non-animated client list instead of the global "snap to 0.01ms" jump. At `t = end`, the second group sits at `x = 0` — exactly where the first group started — so the next animation cycle is visually indistinguishable from the previous frame. The duplicate carries `aria-hidden="true"` so screen readers don't read the client list twice. ## Verification - `pnpm typecheck`, `pnpm format`, `npx eslint` on changed files: clean. - Geometry verified at runtime on desktop (1440×900) and mobile (390×844): copy widths match, second copy lands at `x = 0` at animation end. - New Playwright regression tests (`apps/website/e2e/responsive.spec.ts`) pause the CSS animation, sample bounding rects at `t=0` and `t≈duration`, and assert the seam invariant — covering desktop forward, mobile forward, and mobile reverse marquees. All 5 SocialProofBar tests pass on both `desktop` and `mobile` projects. - Reduced-motion behavior verified in the browser: `animationName: none`, `transform: none`, tracks at their natural positions. Fixes FE-649 ## Screenshots     ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12139-fix-seamless-SocialProofBar-marquee-loop-35d6d73d36508141b6ccf0167016b8c8) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
603dd3eb4e |
fix(assets): recognise m4v and mkv as video extensions (#12088)
## Summary `.m4v` and `.mkv` files render as a generic file icon in the assets sidebar instead of a video preview because `VIDEO_EXTENSIONS` doesn't list them, even though both formats are widely produced by ComfyUI custom nodes and are browser-playable when written with common codecs. ## Changes - **What**: Add `m4v` and `mkv` to `VIDEO_EXTENSIONS` in `packages/shared-frontend-utils/src/formatUtil.ts` and extend the existing test cases. Aligns with ComfyUI core's canonical video extension list (`tests-unit/folder_paths_test/filter_by_content_types_test.py:13`), which includes both. The frontend's format registry at `src/platform/workflow/core/types/formats.ts` also lists `.m4v` with mime `video/x-m4v` — `formatUtil.ts` was the inconsistent surface. - **Breaking**: None. - **Dependencies**: None. ## Review Focus `m4v` is Apple's MP4 container variant; `mkv` is the Matroska container. ComfyUI custom nodes most commonly produce both with H.264/VP9 codecs, which play natively in modern browsers via `<video>`. Adding the extensions routes those files through the existing `MediaVideoTop` component without any new rendering logic. If a user's `.mkv` happens to use an exotic codec the browser can't play (e.g. H.265/HEVC in Chrome), they get the same controllable failure mode as today's `.avi` entries — a `<video>` element with the browser's native unsupported-source UI. That is no worse than the current "show a generic file icon" behavior, and strictly better in the common case. ## Screenshots (if applicable) > **Note**: Screenshots taken from the OSS *input* assets sidebar with [#12086](https://github.com/Comfy-Org/ComfyUI_frontend/pull/12086) also applied locally. That PR fixes a separate regression where OSS input filenames carry a `[input]` annotation suffix that breaks all extension-based media detection — without it, `m4v`/`mkv` files (and every other file in that sidebar) still render as the generic icon. This PR alone is sufficient for cloud assets and OSS output history; the input-sidebar previews require both PRs. Before: <img width="1197" height="714" alt="2026-05-09-031123_hyprshot" src="https://github.com/user-attachments/assets/5c6ebc2d-aac2-411f-a2e4-51a111033184" /> After: <img width="1042" height="723" alt="2026-05-09-031005_hyprshot" src="https://github.com/user-attachments/assets/f0acc2cf-8571-4fd0-b0cd-2b8b87ff9b74" /> |