mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 06:10:32 +00:00
64c75bfce5eff3a798338dcfcd2dcec4fab7e5d5
8051 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
64c75bfce5 |
test: avoid job history double setup (#12324)
## Summary Avoid a second `comfyPage.setup()` in the job history browser tests by registering the initial jobs route mocks before the normal page boot. ## Changes - **What**: Adds an `initialJobsScenario` Playwright option with an auto fixture for the job-history spec, moves QPOV2 setup into `initialSettings`, and keeps the sidebar helper focused on UI navigation. - **Dependencies**: None ## Review Focus Confirm the auto fixture ordering matches the intended browser-test setup: initial `/api/jobs` mocks should be installed before the `comfyPage` fixture performs its normal setup. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12324-test-avoid-job-history-double-setup-3656d73d365081778e24c11d3b65cbef) by [Unito](https://www.unito.io) |
||
|
|
cfd3f9e67b |
fix: classify PLY assets as 3D media (#12319)
## Summary Classify `.ply` as 3D media so PLY outputs are surfaced by queue/assets preview flows. ## Changes - **What**: adds `.ply` to shared 3D extension detection and falls back to the asset `/view` URL when opening 3D assets without `preview_url`. - **Breaking**: none. - **Dependencies**: none. ## Review Focus - This is the tactical FE fix for FE-129; it intentionally does not solve the broader 3D media vs load3d-loadable split. - Assets sidebar 3D viewer still prefers `preview_url`, but now has a usable fallback for assets that only have the normal asset URL. FE-129 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12319-fix-classify-PLY-assets-as-3D-media-3646d73d365081218f0bde401b1601bd) by [Unito](https://www.unito.io) |
||
|
|
8af8a5f0b1 |
test: add tests for workflow extraction util (#12218)
## Summary Adds test coverage for workflow extraction utils ## Changes - **What**: - tests! ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12218-test-add-tests-for-workflow-extraction-util-35f6d73d36508189b15accc9f724d348) by [Unito](https://www.unito.io) |
||
|
|
3b37488eee |
fix: keep node context menu overflow visible when content fits (#12035)
## Summary Stops the Shape submenu (and any other PrimeVue nested submenu) from being clipped behind the node context menu when the menu fits in the viewport. ## Changes - **What**: `constrainMenuHeight` in `NodeContextMenu.vue` now applies `max-height` + `overflow-y: auto` to the root `<ul>` only when `scrollHeight > availableHeight`. The common case keeps `overflow: visible`. - Added `browser_tests/tests/nodeContextMenuShapeSubmenu.spec.ts` regression spec. ## Review Focus Root cause: setting only `overflow-y: auto` on a `<ul>` coerces `overflow-x` to a non-visible value per CSS spec (`If one of overflow-x/overflow-y is visible and the other isn't, the visible value is computed as auto`). PrimeVue `ContextMenuSub` renders submenus in-tree as a nested `<ul>` with `position: absolute; left: 100%`, so the implicit horizontal clip hides them entirely. The pre-existing overflow scenario (#10824 / #10854) is unchanged — when the menu actually overflows, the clamp still applies and `nodeContextMenuOverflow.spec.ts` continues to verify scroll. Submenu clipping in that overflow case is a known limitation, not introduced by this PR. Fixes FE-570 ## screenshot ### AS IS <img width="788" height="505" alt="Screenshot 2026-05-07 at 12 43 26 PM" src="https://github.com/user-attachments/assets/36d34070-0c57-4385-a130-0394f22f282e" /> ### TO BE <img width="779" height="627" alt="Screenshot 2026-05-07 at 12 42 44 PM" src="https://github.com/user-attachments/assets/00956729-763b-4787-822f-209e8ea42331" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12035-fix-keep-node-context-menu-overflow-visible-when-content-fits-3586d73d365081ad9aaec82f220d401c) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
42dcb1cf7b |
1.45.10 (#12321)
Patch version increment to 1.45.10 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12321-1-45-10-3656d73d365081c684e2c76ce92e2ff8) 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.10 |
||
|
|
4931b0c4b2 |
fix(website): add dark-background favicon for legibility in search results (#12285)
*PR Created by the Glary-Bot Agent* --- ## Summary The comfy.org favicon was reported as illegible in Google search results. The current `logomark.svg` is a transparent yellow "C" — when Google (or any client) composites it onto a white surface (search results, light-theme tab strips), the yellow disappears into the background. Fix: ship a dedicated `/favicon.svg` that wraps the existing yellow logomark in a solid black square, and point `<link rel="icon">` at it. The in-page nav logo, `Organization.logo` Schema.org URL, and any other consumer of `logomark.svg` are left untouched, so transparent-composite contexts (knowledge panels, dark nav) continue to render cleanly. ## Changes - `apps/website/public/favicon.svg` *(new)* — 48×48 SVG: black square + scaled-down original logomark path. Existing path geometry is reused verbatim inside a `<g transform>` so the C glyph is byte-identical to the source. - `apps/website/src/layouts/BaseLayout.astro` — `<link rel="icon" href="/icons/logomark.svg">` → `<link rel="icon" href="/favicon.svg">`. One-line change. ## Why a new file (vs editing `logomark.svg` in place) `logomark.svg` is also used by `SiteNav.vue` (in-page header on the dark `--color-primary-comfy-ink` background) and by the JSON-LD `Organization.logo` URL. Both consumers want the transparent version. Editing it in place would draw an ugly black square in the site's own header. ## User report > "just google searched comfyui and logo isnt legible. We should update.." ## Verification **Built site** - `pnpm typecheck` (astro check): 0 errors, 0 warnings - `pnpm build` (astro build): 280 pages built, exit 0 - Built `dist/index.html` contains exactly one `<link rel="icon" href="/favicon.svg">` and zero references to the old icon path in `<head>` - `oxlint` on changed `.astro` file: 0 warnings, 0 errors **Visual (Playwright on local astro dev server)** - New favicon renders correctly at 16/32/64 px — yellow C centered on black square, no clipping. - In-page nav logo unchanged (yellow C floats cleanly on the dark `--color-primary-comfy-ink` nav background, no black wrapper visible). - Mock of Google search-result row shows the new favicon is high-contrast inside Google's white circular wrapper; the old one is nearly invisible. ## Screenshots ### Google-style search result simulation (before / after)  ### Favicon at native sizes + Google circular wrapper  ### In-page nav header (unchanged after the fix)  ## Notes for reviewers - The change deliberately uses pure black `#000` (matching the user's literal request "make the white background, black") rather than `--color-primary-comfy-ink` (`#211927`). Either would work; happy to switch if brand preference is the ink color. - Search-engine cached favicons can take days/weeks to refresh on Google's side after the new file is deployed. ## Screenshots    ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12285-fix-website-add-dark-background-favicon-for-legibility-in-search-results-3616d73d365081babbcbedf0b86d3d67) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
fc7e6a0935 |
fix(terminal): resync logs console on backend reconnect (#12270)
## Summary When the built-in logs terminal stayed open during a backend restart, the buffer froze on pre-restart entries and live log streaming silently stopped — only closing and reopening the panel resynced. Listen for the api `reconnected` event and rebuild the terminal contents the same way a fresh open would. ## Changes - **What**: - Extract `useLogsTerminal` composable. The SFC is now a thin shell holding `terminal: shallowRef<Terminal>` and forwarding to the composable, so `onMounted`/`onScopeDispose` no longer rely on the child's emit callback timing. - Subscribe to `api`'s `reconnected` event via `useEventListener`, registered synchronously before any awaits. On reconnect: `terminal.reset()` → refetch raw logs → `scrollToBottom()` → `subscribeLogs(true)` (the backend loses the per-client subscription on restart, so re-subscribe is required for live streaming to resume). - Wrap in-flight resync/mount fetches in AbortControllers. Overlapping reconnects abort the prior resync, and unmount mid-fetch suppresses writes to the disposed xterm. - Hide BaseTerminal whenever `errorMessage` is set so the error layout doesn't expose an empty xterm container behind the message; `loading=false` after both load failure and resync success so a later successful reconnect can clear a stuck spinner. - Migrate the load/resync error strings to vue-i18n (`logsTerminal.loadError`, `logsTerminal.resyncError`). ## Review Focus - **Re-subscribe is the non-obvious half of the fix** — without it, even after the WebSocket reconnects the backend never resumes streaming logs to this client because its subscription state was wiped on restart. The visible "stale buffer" is only one symptom; the silent "no new logs" symptom needed the explicit `subscribeLogs(true)` re-call in resync. - `terminal.reset()` lives after a successful raw-logs fetch (not before) so a failed resync leaves the prior buffer visible instead of blanking it; resync errors surface via the same inline error message the mount path uses. - 8 unit tests around the composable: mount + subscribe, resync ordering (reset → write → scroll → subscribe via `invocationCallOrder`), in-flight resync abort on double reconnect, resync error surfacing, mount-failure-then-recovery, unmount-mid-fetch terminal-write suppression, listener cleanup on unmount. - 2 E2E tests using `ws.close()` on the proxied WebSocket as the reconnect trigger and `subscribeLogs` HTTP fetch count as the sync point (same pattern as `wsReconnectStaleJob.spec.ts`). Red-checked: disabling the `reconnected` listener fails exactly the two new tests, all 8 pre-existing tests stay green. Fixes FE-712 ## Screenshots Before - (After rebooting, the console window does not update from its state before the reboot must remount the console window for it to resync.) https://github.com/user-attachments/assets/b1e49c2c-89a4-4a4a-82b4-064412acee12 After - (The console window syncs automatically after a reboot.) https://github.com/user-attachments/assets/54b582c5-ad42-41c0-9886-18f4495859da ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12270-fix-terminal-resync-logs-console-on-backend-reconnect-3606d73d3650812fb13fd1934c632344) by [Unito](https://www.unito.io) |
||
|
|
a97f46b497 |
test: cover job history sidebar with typed route mocks (#12272)
## Summary Add the first product-area browser coverage on top of the merged typed route mock foundation: the docked job history sidebar. ## Changes - **What**: Adds `browser_tests/tests/sidebar/jobHistory.spec.ts` using `jobsRouteFixture`. - **What**: Covers direct sidebar entry, docked QPO history entry, terminal history jobs, active queue jobs, tab filtering, search, clear queue, and clear history. - **What**: Adds typed `POST /api/queue` and `POST /api/history` route helpers that validate request bodies with generated zod schemas. - **What**: Adds stable test ids for the job history sidebar and queue progress overlay so tests avoid structural CSS selectors. - **Dependencies**: Builds on the typed route mock foundation merged in #12267. ## Review Focus Review the product assertions and whether this is the right first coverage slice on top of the typed route mock foundation. This PR intentionally avoids asset sidebar and floating QPO lifecycle coverage; those should remain follow-up PRs. ## Screenshots (if applicable) Not applicable. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12272-test-cover-job-history-sidebar-with-typed-route-mocks-3606d73d3650817481d5f9fac4bfc93c) by [Unito](https://www.unito.io) |
||
|
|
448ad73fae |
refactor(assets): collapse useMediaAssets factory wrapper (FE-727) (#12283)
## Summary https://linear.app/comfyorg/issue/FE-727/refactor-usemediaasset `useMediaAssets` was a factory wrapper that branched on `isCloud` and returned one of two near-identical implementations (`useAssetsApi` / `useInternalFilesApi`). Both implementations delegated to the same `assetsStore` actions (`updateInputs` / `updateHistory` / `loadMoreHistory`) — the real cloud/local fork lives inside `assetsStore.fetchInputFiles` (line 121), not at the composable layer. Collapse the wrapper: 1. Delete `useInternalFilesApi` (identical to `useAssetsApi`). 2. Delete `useMediaAssets` (now a pass-through). 3. Switch the four callers to `useAssetsApi` directly. ## Changes - **What**: - Delete `useInternalFilesApi.ts` and `useMediaAssets.ts`. - `AssetsSidebarTab.vue`, `WidgetSelectDropdown.vue`, `useOutputHistory.ts`: call `useAssetsApi` directly. - `useWidgetSelectItems.ts`: narrow `outputMediaAssets` prop type to `IAssetsProvider` (the interface `useOutputHistory` already implements directly). - Test mocks repointed from `useMediaAssets` → `useAssetsApi`. - Two unrelated tailwind class-order lint errors auto-fixed by `pnpm lint:fix` to keep CI green. - **Breaking**: None — no behavior change. ## Review Focus The real cloud/local fork remains at `assetsStore.ts:121` (`fetchInputFiles = isCloud ? fetchInputFilesFromCloud : fetchInputFilesFromAPI`). That branch is M1-strict and clears once BE-933 lands. This PR only collapses the dead wrapper layer above it. `IAssetsProvider` is intentionally kept — `useOutputHistory.ts:83` directly implements it for a different use case, so the interface still has more than one consumer. Surfaced while working on the FE-678 cloud/local asset branching survey. ## Screenshots (if applicable) N/A — no UI change. --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
cf267acffe |
fix(cloud): stop bouncing working users to /cloud/survey mid-session (#12301)
## Summary `getSurveyCompletedStatus` (auth.ts) now resolves ambiguous responses to "completed" instead of "not completed", so transient backend errors no longer bounce working users to `/cloud/survey`. | Backend response | Old behavior | New behavior | |---|---|---| | 200 with non-empty `value` | `true` (completed) | `true` (completed) | | 200 with empty `value` | `false` (not completed) | `false` (not completed) | | 404 | `false` → bounced | `true` (treat as completed) | | 5xx | `false` → bounced | `true` (treat as completed) | | 401 / 403 | `false` → bounced | `true` (treat as completed) | | Network error | `false` → bounced | `true` (treat as completed) | Only a definitive `200` with empty `value` is treated as "not completed". Everything else fails open. The dedicated auth layer handles re-authentication on the next API call, so 401/403 doesn't need a separate branch here. ## Why User reports from team-plan customers: _"I was working in a workflow, hit run, and then got logged out and redirected to a survey screen."_ Datadog shows ~7,000 distinct users/day hitting the `setting key onboarding_survey not found` path on prod ingest. With `onboarding_survey_enabled: true` in prod dynamic config and the catch-all `!response.ok` returning `false`, any mid-session reload tripped a redirect to `/cloud/survey`. User-validated requirement: rather miss showing the survey to a few users than show it duplicately or interrupt working customers. ## Trade-off worth product review A genuinely brand-new user whose `User.Settings` JSON is empty also returns 404 from `/api/settings/onboarding_survey` — the backend doesn't distinguish "key absent for existing user" from "user has no settings yet". With this change, that 404 is treated as "completed", so the survey gate does not fire on the strict 404 path. New users will still see the survey if signup pre-populates the `onboarding_survey` key with an empty object (`200` with empty `value`); if not, the survey is missed on initial signup. We picked this trade-off per the product call that false positives (bouncing paying customers) are strictly worse than false negatives (occasionally missing a new user). The clean fix to recover the new-user signal is a backend change: return `200` with `value: null` when the `User` row exists but the key is absent — distinguishing "no survey saved" from "user not found". Out of scope for this PR; filing as follow-up if accepted. ## Test plan - [ ] Logged-in user with completed survey navigates around — no redirect - [ ] Logged-in user with no survey, fresh tab — redirected to `/cloud/survey` (gate still works for new sessions) - [ ] Logged-in user with no survey, after submitting — no redirect on next nav - [ ] Simulate transient 5xx on `/api/settings/onboarding_survey` (DevTools blocking) — user stays on current page, no redirect Unit coverage in `auth.test.ts` locks the resolution table above against drift (one test per branch, 8 total). ## Companion PRs None — frontend only. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12301-fix-cloud-stop-bouncing-working-users-to-cloud-survey-mid-session-3616d73d365081128ba7e266ad7ccff9) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
4e07fe3a43 |
feat(website): update Terms of Service to legal-approved 2026-05-13 copy (#12286)
*PR Created by the Glary-Bot Agent* --- ## Summary Replaces the `tos.*` i18n keys in `apps/website/src/i18n/translations.ts` with the legal-approved Terms of Service copy from `Comfy - Terms of Service (GP 5.12.26).docx` and surfaces an effective date below the hero on `/terms-of-service`. - Restructures the ToS into 14 sections (intro + 13 numbered sections) to match the new legal-approved structure. - Adds two new keys, `tos.effectiveDateLabel` and `tos.effectiveDate`, rendered as a centered `Effective Date: May 13, 2026` line between the hero and the content (matches the pattern used on the Affiliate Program Terms page). - Subsection labels (*Right to Access and Use Comfy Products.*, *Customer Data.*, etc.) render as h3 headings via the existing `block.N.heading` shape — no changes to `ContentSection.vue` or `contentSections.ts`. - English page meta description tightened to reflect the new scope (Comfy Products: Cloud, API, Enterprise — explicitly excluding Comfy OSS). ## Verbatim legal copy Per request, the copy is **verbatim from the legal-approved `.docx`**, including: - `[URL]` placeholder in §2.7 (Data Retention) where the legal doc has a placeholder pending the real docs page. - `[Address]` placeholder in §12.8 (Notices) where the legal doc has a placeholder pending the finalized mailing address. - Mixed casing in §8 (Disclaimer) and §9 (Limitation of Liability) — e.g. `THE Comfy Products AND OUTPUT…`, `…TOTAL LIABILITY OF Comfy…` — preserved exactly as the legal doc presents it. - §11(c) cross-reference left as written. These are intentional and flagged for follow-up with legal/docs before publishing. I have **not** silently substituted real values for the placeholders or normalized casing — that would be editing legal-approved text. ## Chinese (zh-CN) handling The legal-approved copy was provided in English only. To avoid serving English text under a Chinese page shell: - `apps/website/src/pages/zh-CN/terms-of-service.astro` is **removed**. - `getRoutes()` in `apps/website/src/config/routes.ts` treats `termsOfService` as locale-invariant, so the Chinese footer link emits `/terms-of-service` directly — no redirect hop. - `astro.config.ts` adds a redirect from `/zh-CN/terms-of-service` → `/terms-of-service` as a safety net for any stale external/cached links. - All `zh-CN` values on the new `tos.*` keys are filler (mirrored from English) so the `Record<Locale, string>` type contract holds; they are never served. ## Files changed - `apps/website/src/i18n/translations.ts` — 73 old `tos.*` keys removed, 136 new keys added matching the .docx structure. - `apps/website/src/pages/terms-of-service.astro` — imports `t`, renders effective date, updates meta description. - `apps/website/src/pages/zh-CN/terms-of-service.astro` — **removed**. - `apps/website/astro.config.ts` — adds `/zh-CN/terms-of-service` → `/terms-of-service` redirect. - `apps/website/src/config/routes.ts` — `termsOfService` route stays un-prefixed in non-English locales. ## Verification - `pnpm --filter=@comfyorg/website typecheck` — 0 errors (2 pre-existing hints in unrelated files). - `pnpm --filter=@comfyorg/website build` — 279 pages built, `/terms-of-service/` (English page) and `/zh-CN/terms-of-service/` (redirect stub with `noindex` + `canonical`) both emitted. - Pre-commit lint-staged ran `oxfmt`, `oxlint --type-aware`, `eslint --fix`, and `pnpm typecheck` on every commit — all green. - Rendered HTML spot-checked: English `/terms-of-service` contains the new content with verbatim `[URL]` and `[Address]` placeholders; zh-CN homepage footer now links directly to `/terms-of-service` (no redirect hop); `/zh-CN/privacy-policy` and other locale routes still correctly emit `/zh-CN/…` prefixes. - Manual visual check via `astro preview` + Playwright — sidebar nav, h2 section titles, h3 subsection headings, paragraph wrapping, and inline mailto/href anchors all render correctly. Screenshots attached. ## Code-review follow-ups addressed - **zh-CN regression** — Page removed, route override added, redirect kept as safety net. - **Page description mismatch** — Updated meta description to reflect new scope. - **`docs.comfy.org/data-retention` 404** — Now matches the docx placeholder `[URL]`; flagged to legal/docs. - **Disclaimer / Liability casing** — Restored to match docx verbatim. - **Mailing address** — Now matches the docx placeholder `[Address]`; flagged to legal. - **Section 11(c) cross-reference** — Left verbatim per legal doc. ## Scope notes - English-only legal update per request — no Chinese rewrite, no schema changes, no acceptance-tracking infrastructure. - The signup-flow link on `platform.comfy.org` (`website` repo) already points at `https://www.comfy.org/terms-of-service` and renders the new copy at the same URL — no change needed there. ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12286-feat-website-update-Terms-of-Service-to-legal-approved-2026-05-13-copy-3616d73d3650815b9262f84d12655dfa) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
3e31de5bbb |
test: migrate MaxHistoryItems browser coverage (#12298)
## Summary Migrates the MaxHistoryItems browser coverage to the accepted jobs route fixture pattern. ## Changes - **What**: Composes `jobsRouteFixture` into the queue settings spec and removes the old `AssetsHelper` route setup. - **What**: Adds a `responseLimit` option to `jobsRouteFixture` so tests can match a requested history limit while intentionally returning more jobs. - **Dependencies**: None. ## Review Focus The key behavior is preserving both FE-501 acceptance cases: `/api/jobs` still receives the configured `limit`, and the queue panel still caps rendered history even if the mocked backend returns more rows than requested. Fixes FE-501 ## Screenshots (if applicable) Not applicable. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12298-test-migrate-MaxHistoryItems-browser-coverage-3616d73d365081d6bf77fb205fcd51d4) by [Unito](https://www.unito.io) |
||
|
|
0558740c78 |
refactor: migrate default combo widget select to Reka (#12288)
## Summary Migrate the default combo widget select from the PrimeVue `SelectPlus` wrapper to a Reka `Combobox` implementation while preserving the existing Comfy combo widget contract and the node-canvas dropdown behavior. ## Changes - **What**: Rewrites `WidgetSelectDefault.vue` on top of Reka `ComboboxRoot`, `ComboboxTrigger`, `ComboboxInput`, `ComboboxContent`, and `ComboboxItem`. - **What**: Preserves the default combo widget surface: `v-model`, `widget` prop, `aria-label` from the widget name/label, `data-capture-wheel`, disabled state, placeholder/filter placeholder, default slot controls, invalid current value display, array values, dynamic/factory values, and `getOptionLabel` fallback behavior. - **What**: Keeps dynamic `values` compatibility by refreshing function-backed options when the dropdown opens, without re-evaluating the factory on every search keystroke. - **What**: Deletes the now-unused PrimeVue `SelectPlus.vue` wrapper and removes the PrimeVue test plugin/stub path from the default widget select tests. - **What**: Updates App Mode dropdown clipping coverage and combo-widget browser coverage to target the new Reka overlay/viewport structure. - **Breaking**: No breaking change is intended for the documented Comfy combo widget contract. This migration does not preserve incidental PrimeVue `Select` prop pass-through from `widget.options`; that was a side effect of wrapping PrimeVue rather than a stable widget API. - **Dependencies**: No new dependencies. ## Review Focus ### Compatibility choices The goal of this PR is a migration PR, not a broad behavior redesign. The new implementation keeps the Comfy-specific combo contract rather than attempting to emulate PrimeVue internals. In particular: - `values` still accepts arrays and functions, and function values are re-read on open to support dynamic/custom node option sources. - `getOptionLabel(value) || value` is intentionally preserved to match the sibling dropdown path and avoid turning an empty-string label into a blank rendered option. - Invalid/current values that are not present in the option list are still rendered in the trigger instead of disappearing. - `WidgetWithControl` continues to render its default slot in the control area, with trigger text truncation preserved. - App Mode `OverlayAppendToKey='body'` continues to map to a body portal to avoid panel clipping. ### Visual alignment and screenshot updates The previous PrimeVue implementation passed `size="small"`, which injected internal `.p-select-sm .p-select-label` styling. That internal PrimeVue style used its own small-select font size and padding, overriding the surrounding widget sizing intent and making the select trigger subtly taller with slightly larger text than nearby inline node widget controls. The Reka implementation intentionally keeps the normal widget styling path instead of recreating that PrimeVue-specific internal override. This means the trigger follows the same inline widget sizing direction as neighboring controls rather than preserving the incidental PrimeVue height/text-size delta. Because this is an expected visual difference from the migration, the affected E2E screenshots should be recaptured instead of treating the old PrimeVue select height as the target. ### Scrollbar and focus behavior Reka provides the combobox/listbox semantics we want, including search, arrow navigation, highlighted items, and Enter selection. The tricky part is the canvas dropdown scrollbar behavior. The native Reka viewport path hides/owns scrollbar behavior in a way that made it hard to preserve the previous widget dropdown affordances, especially visible scrollbars and mouse wheel capture over the node canvas. To keep the previous behavior, this PR renders a dedicated scrollable viewport inside `ComboboxContent` with the project scrollbar utilities (`scrollbar-thin`, stable gutter, transparent track). That preserves visible scroll affordance and allows wheel events over the dropdown to scroll the list instead of zooming the canvas. There was one Reka interaction to account for: pressing the native scrollbar can be treated as a focus-outside event from the search input, which previously closed the dropdown on mouse down or caused subsequent wheel events to leak back to the canvas. The new `useRestoreFocusOnViewportPointer` composable handles only that short pointer gesture: - viewport pointerdown marks a short-lived scrollbar/viewport interaction, - the next focus-outside event is prevented only if the search input can be restored, - the guard is cleared by `pointerup`, `pointercancel`, and a timeout so normal outside clicks still close the dropdown. ### Tests and regression coverage Unit coverage was updated around the new Reka implementation: - option sources from arrays and functions, - dynamic values refreshed on open but not on each search keystroke, - selection updates and blank/undefined Reka emissions being ignored, - search filtering and Reka keyboard selection behavior, - disabled state, invalid current values, `getOptionLabel`, empty results status, and WidgetWithControl slot preservation, - composable coverage for pointerup, pointercancel, repeated pointerdown listener cleanup, and no-input/no-op behavior. Browser regression coverage now checks the canvas-specific interaction surface: - opening and selecting default combo widget options, - wheel over the dropdown scrolls the list instead of zooming the canvas, - pressing the scrollbar does not close the dropdown, - wheel capture still works after pressing the scrollbar, - opening another node widget closes the previous dropdown, - switching between node widgets preserves dropdown scroll capture, - serialize/reload retains selected combo values. ## Screenshots (if applicable) New <img width="527" height="753" alt="스크린샷 2026-05-18 오전 1 36 27" src="https://github.com/user-attachments/assets/2293d510-6965-4b84-9b12-b8528f8a734f" /> Old <img width="496" height="473" alt="스크린샷 2026-05-18 오전 1 35 57" src="https://github.com/user-attachments/assets/47c0e28a-27df-44a6-81a8-14fcc1f3bd8f" /> Reka Supports Auto highlight top item on search (Search -> Enter -> Select 👍) https://github.com/user-attachments/assets/9d633dfc-c23a-4e7a-8d39-b044c219f1f3 The default combo widget trigger has a small intentional visual delta from the old PrimeVue path because the Reka implementation does not recreate PrimeVue's internal `size="small"` label override. https://github.com/user-attachments/assets/a9053a14-e39e-4d5e-a846-dcf9aeb0caed ## Validation - `pnpm format` - `pnpm lint` (passes; existing warning-only lint output remains in unrelated tests) - `pnpm typecheck` - `pnpm typecheck:browser` - `pnpm test:unit` - `PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://127.0.0.1:5174 pnpm exec playwright test browser_tests/tests/vueNodes/widgets/combo/comboWidget.spec.ts --project=chromium` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12288-refactor-migrate-default-combo-widget-select-to-Reka-3616d73d365081fd8742c038a7dc7851) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
8562816ffa |
1.45.9 (#12303)
Patch version increment to 1.45.9 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com>v1.45.9 |
||
|
|
5133ab6cf7 |
FE-416: route 3D node outputs to owner workflow on tab switch (#12295)
## Summary When a workflow queues a 3D job (Preview3D / Load3D / SaveGLB) and the user switches to another tab before the job completes, the WS `executed` payload was silently dropped: `getNodeByExecutionId(app.rootGraph, …)` resolves against the currently-visible workflow's graph, so the node isn't found, `onExecuted` never fires, and the workflow JSON never learns the path of the asset the backend just saved. Worse, `setNodeOutputsByExecutionId` for top-level execution ids returns the id verbatim without consulting rootGraph, so the old handler also wrote the payload into the *active* (wrong) workflow's `app.nodeOutputs`, polluting it and getting mis-attributed by `ChangeTracker` snapshot/restore on subsequent tab switches. This change introduces a single routing point — no new public API, no new in-memory cache layer — that re-uses three pieces of infrastructure that already exist independently: 1. `executionStore.jobIdToSessionWorkflowPath` already records which workflow queued each prompt_id. 2. Each `ComfyWorkflow.ChangeTracker` already snapshots `nodeOutputs` on `deactivate()` and replays it through the `app.nodeOutputs = …` setter on `restore()`, which fires `onNodeOutputsUpdated` for every extension. 3. Audio nodes already implement `onNodeOutputsUpdated` to rehydrate their widget from `nodeOutputs`. They have always been silently broken in this same scenario (no one noticed because missing audio is less visible than a missing 3D model); the fix here repairs audio for free as a side effect. The new behaviour: - `app.ts` looks up the node against the active rootGraph *first*. Only when found do we touch `nodeOutputStore` and call `node.onExecuted` (existing path, unchanged). Moving the `nodeOutputStore` write inside `if (node)` is the part that eliminates the cross-workflow pollution above. - When the node is not in the active rootGraph, the new `routeExecutedToInactiveOwner` helper finds the owner workflow via `jobIdToSessionWorkflowPath`, then writes the payload directly into `owner.changeTracker.nodeOutputs` — the same field that `restore()` already drains on tab switch back. - Preview3D / SaveGLB add `onNodeOutputsUpdated` (mirroring the pattern audio uses), normalise the path, write `node.properties['Last Time Model File']` (already workflow-JSON-serialised), and apply it to the viewer. The legacy `node.onExecuted` chain stays intact for live active-workflow executions — both paths are idempotent. ## Screenshots (if applicable) Before https://github.com/user-attachments/assets/3803af1f-2eb6-41af-87ed-ac885a2eaad6 After https://github.com/user-attachments/assets/72e1bed9-5f94-414d-ac31-fc925651d11b ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12295-FE-416-route-3D-node-outputs-to-owner-workflow-on-tab-switch-3616d73d3650817b908de48a32b1d6bd) by [Unito](https://www.unito.io) |
||
|
|
058acfe592 |
test: add basic E2E tests for CurveEditor widget (#10730)
## Summary Add Playwright tests covering default rendering, interpolation selector, and click-to-add-point interaction. Includes test workflow asset. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10730-test-add-basic-E2E-tests-for-CurveEditor-widget-3336d73d365081f5a403df837737bc12) by [Unito](https://www.unito.io) |
||
|
|
3b79917011 |
fix(website): restore registry metadata for cloud nodes catalog (#12307)
*PR Created by the Glary-Bot Agent* --- ## Summary The [`/cloud/supported-nodes`](https://comfy-website-preview-pr-12271.vercel.app/cloud/supported-nodes) page was rendering packs without descriptions, icons, or download counts (PR #12271 preview, [`Release: Website` run](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/25866708684/job/76010642686)). The registry enrichment in `apps/website/src/utils/cloudNodes.registry.ts` was silently failing for **two** reasons: 1. **Missing `limit` query parameter.** `api.comfy.org /nodes` applies a default page size of `10` when no `limit` is sent. Each batch of up to 50 `node_id` filters was therefore truncated to 10 results, dropping metadata for every pack past the first ten. 2. **Schema rejected `null` for optional arrays.** The registry serializes empty server-side slices as JSON `null`, so any pack with `supported_os: null` or `supported_accelerators: null` failed Zod validation — and because parse failure is not retryable, the **entire batch** got `null` enrichment. Both bugs produce the same user-visible symptom ("packs fetched, but no metadata"), so they were entangled. ## Changes (2 files) - `cloudNodes.registry.ts`: send `?limit=<batch length>` on every batch and accept `null` for all optional registry fields. The schema normalizes `null → undefined` at the parse boundary via `.transform()`, so the parsed shape continues to match the generated OpenAPI `Node` type contract; downstream code (`toDomainPack`, the rendered `Pack`) is unchanged. - `cloudNodes.registry.test.ts`: two new regression tests: - Server-side default page size: simulates the pre-fix behavior (default `limit=10` truncates) and asserts all 30 batched IDs are enriched. - `null` registry fields: asserts `null` values are normalized to `undefined` on the parsed pack. ## Verification End-to-end fetch against the live registry on this branch (14 packs from the current snapshot): ``` Requested: 14, enriched: 14 comfyui-kjnodes downloads=3,404,416 rgthree-comfy downloads=3,105,034 comfyui-easy-use downloads=2,829,702 comfyui-impact-pack downloads=2,680,589 comfyui_essentials downloads=2,418,367 (supported_os null → undefined) ComfyUI-Crystools downloads=1,729,087 comfyui_layerstyle downloads=1,696,809 comfyui_ultimatesdupscale downloads=1,478,763 comfyui_ipadapter_plus downloads=1,236,442 (supported_os null → undefined) was-node-suite-comfyui downloads=993,960 (supported_os null → undefined) comfyui-advanced-controlnet downloads=600,849 comfyui-animatediff-evolved downloads=503,831 comfyui-cogvideoxwrapper downloads=121,716 (supported_os null → undefined) comfyui_steudio downloads=58,470 (supported_os null → undefined) ``` 5 of 14 packs returned `null` arrays — all parse cleanly now. Sort-by-downloads (already implemented in `useFilteredPacks.ts`) becomes meaningful again once `downloads` is populated. Quality gates: - `pnpm --filter @comfyorg/website test:unit` → 77/77 pass (includes 2 new regression tests) - `pnpm --filter @comfyorg/website typecheck` → 0 errors, 0 warnings on changed files - `pnpm exec eslint` on changed files → clean - `pnpm exec oxfmt --check` on changed files → clean ## Follow-ups (separate tickets) - "New" badge + `dateAdded` field for newly added packs. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12307-fix-website-restore-registry-metadata-for-cloud-nodes-catalog-3626d73d365081288a2cfc30003160cf) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
d955625c20 |
fix(model-library): auto-refresh after upload, plus 'r' key fallback (FE-695) (#12257)
## Summary Model Library sidebar now refreshes automatically when an upload completes, and the `r` keybinding refreshes the library in addition to refreshing combo widgets inside graph nodes. ## Changes - **What**: - `modelStore`: new `refreshModelFolder(name)` for surgical reset+reload of one folder, and `refresh()` that re-loads any folders that had been loaded - `ModelLibrarySidebarTab.vue`: watches `assetDownloadStore.lastCompletedDownload` and refreshes the affected folder; the in-panel refresh button now routes through `refresh()` - `Comfy.RefreshNodeDefinitions` (`r` key): also calls `modelStore.refresh()` so the keyboard fallback actually refreshes the Model Library list ## Review Focus - Both `modelStore` and `assetsStore` exist; the upload wizard was only refreshing the latter, which is what caused the bug. Confirm the new watcher path is the right hook (rather than wiring it inside the wizard) — chose this so it also covers completions that happen after the wizard has been closed. - `refreshModelFolder` falls back to `refresh()` (not raw `loadModelFolders()`) for unknown folder types, to avoid dropping other folders' loaded contents. - Generated tab half of the ticket is intentionally **deferred** until BE-885 (cursor pagination on `GET /api/jobs`) lands — AC items around "no duplicates" and "cursor state maintained" depend on it. Fixes FE-695 (Model Library half). ## Screenshots (if applicable) N/A — behavior change verified by unit tests. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12257-fix-model-library-auto-refresh-after-upload-plus-r-key-fallback-3606d73d3650811a8895ef6e3ef2b4b8) by [Unito](https://www.unito.io) |
||
|
|
861d737041 |
FE-702: rehydrate 3D viewer on subgraph re-entry via persistent ready hook (#12294)
## Summary When a Preview3D / Load3D / SaveGLB node lives inside a subgraph, the 3D viewer correctly displays the model the first time you enter the subgraph but is blank after exiting and re-entering — even though `node.properties['Last Time Model File']` is still populated and the underlying file is on disk. Fix: introduce a persistent companion to `waitForLoad3d` in `useLoad3d.ts`: - `onLoad3dReady(callback)` — registers a callback that fires on *every* (re-)initialization of the `Load3d` instance for a given node, not just the first one. Cleared automatically when the node is removed from the graph (chained into `node.onRemoved` alongside the existing `pendingCallbacks` cleanup). - `waitForLoad3d` keeps its original one-shot semantics so callbacks that install per-node side effects (e.g. wrapping `node.onExecuted`, setting `sceneWidget.serializeValue`) do not chain on remount. - When `onLoad3dReady` is registered after a `Load3d` instance already exists, the callback fires synchronously as well, so the same code path covers both initial setup and subsequent rehydrations. Preview3D / Load3D / SaveGLB move the "reapply state from `node.properties` / `model_file` widget to the Load3d viewer" block from `waitForLoad3d` to `onLoad3dReady`. First mount and every subsequent remount now run identical rehydration code, with `node.properties['Last Time Model File']` (already workflow-JSON-serialised) as the single source of truth. ## Screenshots (if applicable) before https://github.com/user-attachments/assets/e4b0fe6f-c898-4210-b545-7ad6883ed722 after https://github.com/user-attachments/assets/a4a28490-071d-4694-87a8-5eaa501ac168 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12294-FE-702-rehydrate-3D-viewer-on-subgraph-re-entry-via-persistent-ready-hook-3616d73d3650811e93e7dedb32762711) by [Unito](https://www.unito.io) |
||
|
|
7160a9ee3f |
fix: QPO progress bar now shows node name in subgraphs (#7688)
## Summary
Resolve the queue progress node label from queued prompt metadata so
subgraph execution IDs show the correct node name without depending on
the live canvas.
## Changes
- **What**: Store a prompt-scoped `executionId -> { title, type }`
lookup from `p.output` when queueing a job, and use that lookup for the
active job's executing node label.
- **What**: Reuse the same job-scoped node info for the browser tab
title so it stays aligned with the queue overlay.
- **What**: Add unit coverage for root and subgraph execution IDs, and
merge the branch forward to current `main`.
## Review Focus
This keeps the fix scoped to the existing singular `activeJobId` path.
It fixes subgraph labels and avoids the workflow-switching regression
from resolving against `app.rootGraph`, but it does not redesign
concurrent multi-job selection yet.
Longer term, the cleaner solution is still prompt-scoped execution
metadata from the backend rather than frontend reconstruction.
## Screenshots (if applicable)
N/A
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
|
||
|
|
71092b2011 |
fix: stop trackpad pinch/swipe gestures from breaking the UI (#12052)
## Summary On macOS trackpads, several browser default gestures were leaking through and breaking the workflow: - **Pinch-zoom on a focused textarea widget** triggered page-level zoom, pushing `position: fixed` UI (notably `ComfyActionbar`) off-screen until a reload. - **Horizontal swipe on a focused textarea widget** triggered browser back/forward, leaving the workflow. - **Pinch / horizontal swipe inside the image picker dropdown** had the same two issues, because PrimeVue `Popover` teleports content to `document.body` and the `LGraphNode` wheel handler never sees the events. Fixes FE-292. ## Why - **`overscroll-behavior: none` on `html, body`** — horizontal swipe to back/forward is decided by the browser at gesture start; JS preventDefault can't reliably beat it. `overscroll-behavior` is the standards-track signal for opting out, and ComfyUI is a full-screen editor that never benefits from native overscroll. - **`useCanvasInteractions` now treats pinch-zoom and horizontal-dominant wheel as canvas gestures** that override widget wheel consumption, so the gesture pans/zooms the canvas instead of falling through to destructive browser defaults. The check is exported as `isCanvasGestureWheel` for reuse. - **`FormDropdownMenu` has its own minimal `onWheel`** that only preventDefaults destructive gestures and deliberately does not forward to the canvas. The dropdown is its own scroll container and shouldn't leak interactions into the editor; vertical scrolling stays native. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12052-fix-stop-trackpad-pinch-swipe-gestures-from-breaking-the-UI-3596d73d3650810aac3fcd8a71b29f9e) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
d05ec230bf |
1.45.8 (#12282)
Patch version increment to 1.45.8 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12282-1-45-8-3616d73d365081efa628de04190d2a92) 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.8 |
||
|
|
d6f632477f |
feat(dialog): migrate Error / NodeSearchBox / SecretForm / VideoHelp / Customization to Reka-UI (Phase 2) (#12109)
## Summary Phase 2 of the dialog migration kicked off in #11719 and continued in #12041. Migrates four medium-complexity dialogs to the Reka-UI primitives. Public API of `useDialogService` / `dialogStore` is unchanged. Parent: [FE-571](https://linear.app/comfyorg/issue/FE-571/dialog-system-migration-primevue-reka-ui-parent) This phase: [FE-574](https://linear.app/comfyorg/issue/FE-574/phase-2-migrate-error-nodesearchbox-secretform-videohelp-customization) Predecessors: #11719 (Phase 0, merged), #12041 (Phase 1, merged) > **NodeSearchBoxPopover deferred** — host of an inner PrimeVue Dialog (filter panel) that teleports to body and conflicts with Reka's DismissableLayer outside-pointer detection (CI dismissed the outer dialog mid-interaction). Tracking as a follow-up PR; FE-574 stays open for it. ## Changes ### `src/services/dialogService.ts` | Call site | Renderer | Size | | --- | --- | --- | | `showExecutionErrorDialog()` | `'reka'` | `lg` | | `showErrorDialog()` | `'reka'` | `lg` | ### `src/components/dialog/content/ErrorDialogContent.vue` - Drops `import Divider from 'primevue/divider'` and `import ScrollPanel from 'primevue/scrollpanel'` - Replaces with `<hr class="border-t border-border-subtle">` + `<div class="h-[400px] w-full max-w-[80vw] overflow-auto">` ### Direct PrimeVue → Reka swaps (no `dialogStore` involvement) | File | Notes | | --- | --- | | `src/components/common/CustomizationDialog.vue` | Reka primitives + DialogTitle/Header/Footer; drops PrimeVue Divider; `:modal="false"` and `pointer-down-outside` overlay guard so the PrimeVue ColorPicker overlay (teleported to body) does not auto-dismiss the dialog | | `src/platform/assets/components/VideoHelpDialog.vue` | Headless Reka content; preserves capture-phase ESC by stopping propagation on `escape-key-down`; `VisuallyHidden` title for a11y | | `src/platform/secrets/components/SecretFormDialog.vue` | Reka primitives, retains `v-model:visible`, autofocus on the provider trigger, form submit/validation | ### Tests - `src/services/dialogService.renderer.test.ts`: extends the regression net to cover both error-dialog call sites (renderer `'reka'`, size `'lg'`) - `src/components/common/CustomizationDialog.test.ts`: swaps PrimeVue Dialog stub for Reka primitive stubs ### screenshot <img width="1236" height="761" alt="Screenshot 2026-05-11 at 10 26 51 PM" src="https://github.com/user-attachments/assets/086cb73f-a98d-41f8-96ee-21922da8dd73" /> <img width="1161" height="786" alt="Screenshot 2026-05-12 at 1 26 39 PM" src="https://github.com/user-attachments/assets/db7383d8-f737-4472-91c0-dab5aa41547b" /> ## Quality gates - [x] `pnpm typecheck` — clean - [x] `pnpm lint` — 0 errors (3 pre-existing warnings unrelated to this PR) - [x] `pnpm format` — applied - [x] `pnpm test:unit` (touched + adjacent areas): - `dialogService.renderer.test.ts` — 5/5 - `CustomizationDialog.test.ts` — 4/4 - All `src/components/dialog` tests — 73/73 - `src/platform/secrets` tests — 39/39 - `NodeBookmarkTreeExplorer.test.ts` — 7/7 - [ ] CI Playwright matrix ## Public API impact None. `useDialogService` / `dialogStore` signatures unchanged. Custom-node extensions calling `app.extensionManager.dialog.*` continue to work. ## Out of scope (later phases) - NodeSearchBoxPopover — follow-up PR under FE-574 - Settings dialog — Phase 3 (FE-575) - Manager dialog — Phase 4 (FE-576) - `ConfirmDialog` callers (`SecretsPanel`, `BaseWorkflowsSidebarTab`) — Phase 5 (FE-577) - Removing PrimeVue `Dialog`/`<style>` overrides in `GlobalDialog.vue` — Phase 6 (FE-578) ## Test plan - [x] Unit: 73/73 dialog-area, 39/39 secrets - [ ] CI: full Vitest + Playwright matrix - [ ] Manual on a backend: - Trigger an execution error → error dialog opens through Reka, scroll body, copy-to-clipboard, close - Add/edit a secret → form submits, validation errors render, ESC and cancel close - Open VideoHelpDialog from `UploadModelFooter` while inside the asset modal → ESC closes only the help dialog - Customize a node bookmark color/icon → apply/reset, color picker overlay works |
||
|
|
1ab63807e9 |
fix: reserve scrollbar gutter on textareas to prevent hover reflow (#12280)
*PR Created by the Glary-Bot Agent* --- Adds the Tailwind 4.3 `scrollbar-gutter-stable` utility to the shared shadcn `Textarea` wrapper and the `ModelInfoPanel` description textarea so the layout no longer shifts when a vertical scrollbar appears on hover or focus. The wrapper edit propagates to all four consumers (`WidgetTextarea`, `WidgetMarkdown`, `ComfyHubDescribeStep`, `ComfyHubCreateProfileForm`). The `ModelInfoPanel` site uses a native `<textarea>` that bypasses the wrapper, so it is fixed inline. The `overflow-hidden hover:overflow-auto` performance pattern from #10804 is intentionally preserved. ## Verification - Typecheck, eslint, oxlint, oxfmt, stylelint clean on changed files - `Textarea.test.ts`, `WidgetTextarea.test.ts`, `WidgetMarkdown.test.ts`, `ComfyHubDescribeStep.test.ts`, `ModelInfoPanel.test.ts` — all 68 tests pass - `pnpm build` succeeds; the production CSS bundle contains the `scrollbar-gutter:stable` rule - Storybook (`UI/Textarea`): confirmed `getComputedStyle(textarea).scrollbarGutter === 'stable'` on Default, WithLabel, and overflowing-content scenarios Fixes FE-697 ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12280-fix-reserve-scrollbar-gutter-on-textareas-to-prevent-hover-reflow-3606d73d3650816f9947e20134abf59e) 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> |
||
|
|
e35bea51d6 |
refactor(browser-tests): centralize template mocking via TemplateHelper (#11837)
## Summary
Centralize template route mocking by mirroring the existing
`AssetHelper` fixture pattern, so tests no longer hand-roll
`page.route('**/templates/index.json', ...)` and
`page.route('**/templates/**.webp', ...)` blocks.
## Changes
- **What**:
- Expand `browser_tests/fixtures/data/templateFixtures.ts` with stable
distribution exports (`STABLE_CLOUD_TEMPLATE`,
`STABLE_DESKTOP_TEMPLATE`, `STABLE_LOCAL_TEMPLATE`,
`STABLE_UNRESTRICTED_TEMPLATE`), an `ALL_DISTRIBUTION_TEMPLATES` set,
and a `generateTemplates(count, distribution?)` bulk generator.
`makeTemplate` and `mockTemplateIndex` are preserved.
- Add `browser_tests/fixtures/helpers/TemplateHelper.ts` modeled on
`AssetHelper`: an immutable `TemplateConfig` driven by composable
operators (`withTemplates`, `withTemplate`, `withCloudTemplates`,
`withDesktopTemplates`, `withLocalTemplates`,
`withUnrestrictedTemplates`, `withRawIndex`), a `TemplateHelper` class
with `mock()` / `mockIndex()` / `mockThumbnails()` / `clearMocks()`, and
a `createTemplateHelper(page, ...operators)` factory.
- Add `browser_tests/fixtures/templateApiFixture.ts` exposing
`templateApi: TemplateHelper` as a Playwright fixture with automatic
`clearMocks()` teardown (mirrors `assetApiFixture`).
- Migrate `browser_tests/tests/templateFilteringCount.spec.ts` to
`mergeTests(comfyPageFixture, templateApiFixture)` and
`templateApi.configure(withTemplates(...))` + `templateApi.mockIndex()`.
The webp thumbnail mock moves into the helper.
- **Breaking**: None. `makeTemplate` and `mockTemplateIndex` exports
remain.
## Review Focus
- `TemplateHelper.mock()` is split into `mockIndex()` and
`mockThumbnails()` so the spec can install the thumbnail handler in
`beforeEach` (where it has no per-test data) and the index handler
per-test (after `configure(...)`). Both still register through
`routeHandlers` so `clearMocks()` unroutes everything.
- Operators are pure (`(config) => config`) and the helper deep-copies
the resulting `templates` array, matching the AssetHelper immutability
pattern.
- The thumbnail route is `**/templates/**.webp` and serves
`browser_tests/assets/example.webp` — identical to the previous inline
behavior.
Fixes #11431
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11837-refactor-browser-tests-centralize-template-mocking-via-TemplateHelper-3546d73d365081beac00d80c2ab5ea1c)
by [Unito](https://www.unito.io)
|
||
|
|
f429e1e0c4 |
refactor: promote FormSearchInput to shared ui as AsyncSearchInput (#12185)
## Summary Follow-up to #12183: move the debounced, searcher-driven search input out of `src/renderer/...` and into the shared primitives folder, so both the graph (form dropdown node widget) and the shell UI (templates dialog, right side panel tabs) can use it without crossing the renderer layer. ## Changes - **What**: Renamed and relocated `FormSearchInput` → `AsyncSearchInput` at `src/components/ui/search-input/AsyncSearchInput.vue`, joining the existing `SearchInput` / `SearchAutocomplete` siblings. - **What**: Updated all 9 callers (graph form dropdown, right side panel tabs, templates dialog) to import from the new path/name. Test file moved alongside the component. - **Breaking**: None — pure rename + relocate, behavior is identical. ## Review Focus - New name reflects the component's distinguishing feature (the async `searcher` lifecycle: debounce + cancellation + loading state); see Slack thread. - Slack thread captured the discussion — splitting from #12183 so the perf fix can backport cleanly to the release line. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12185-refactor-promote-FormSearchInput-to-shared-ui-as-AsyncSearchInput-35e6d73d365081c585d8d421ea4353fa) by [Unito](https://www.unito.io) Co-authored-by: Christian Byrne <cbyrne@comfy.org> |
||
|
|
d6b4137eec |
chore: upgrade tailwindcss to 4.3 (#12275)
*PR Created by the Glary-Bot Agent* --- ## Summary Bump `tailwindcss` and `@tailwindcss/vite` from `^4.2.0` to `^4.3.0` in the workspace catalog so the new first-class scrollbar utilities (notably `scrollbar-gutter-stable`) are available — the missing utility behind the FE-697 workaround. Release notes: https://tailwindcss.com/blog/tailwindcss-v4-3. ## Changes - **What**: - `pnpm-workspace.yaml` catalog: `tailwindcss` and `@tailwindcss/vite` → `^4.3.0` (lockfile resolves to `4.3.0` for `tailwindcss`, `@tailwindcss/vite`, `@tailwindcss/node`, and all `@tailwindcss/oxide-*` native packages). - Auto-fix 21 lint errors that `eslint-plugin-better-tailwindcss` now reports because the new utilities have canonical forms: - `VirtualGrid.vue`: `[scrollbar-gutter:stable]` → `scrollbar-gutter-stable` (the FE-697 case) - `VirtualGrid.vue` + `RightSidePanel.vue`: `scrollbar-thin` class-order fix - `TopBarHeader.vue`: `h-6.25 w-6.25` → `size-6.25` (6 button cells) - `Dialogue.vue`: `translate-x-[-50%] translate-y-[-50%]` → `translate-[-50%]` - Minor `-mx-[…]`, `-inset-[…]`, `-ml-[…]` arbitrary-value reorderings in `NodeSearchFilterBar.vue`, `LGraphNode.vue`, `CloudNotificationContent.vue` - All 21 are auto-fixes; all existed on `main` and would have started failing CI on next merge. - **Breaking**: None. v4.3 is purely additive; existing config (CSS-first `@theme`/`@utility`/`@plugin`, custom `lucideStrokePlugin`, `tailwindcss-primeui`, `@iconify/tailwind4`, `tw-animate-css`) is unchanged. ## Review Focus - Confirmed `scrollbar-gutter: stable` compiles into `dist/assets/index-*.css` from `VirtualGrid.vue` after the canonicalization. - Runtime probe via Playwright (preview build) confirmed `scrollbar-gutter-stable`, `scrollbar-thin`, `tab-4`, and `zoom-100` all apply. - `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm knip` all pass. - `pnpm test:unit`: 10687/10696 pass. 1 failure in `GraphView.test.ts` ("reconnect wiring") confirmed to fail identically on `main` (flaky `toHaveBeenCalledTimes(1)` getting `3`/`4`) — unrelated to this change. - Oracle code review: 0 findings. Refs FE-697. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12275-chore-upgrade-tailwindcss-to-4-3-3606d73d3650813185cece1c7315e1c2) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
b578147714 |
fix(ci): add unmapped to genhtml ignore-errors, remove unreachable placeholder (#12273)
## Summary - Add `unmapped` to genhtml `--ignore-errors` flag to fix GH Pages deploy failure - Remove unreachable placeholder block (dead code cleanup) ## Problem PR #11381 added `--ignore-errors source` but genhtml is now failing with a different error: ``` genhtml: ERROR: no data for line:4291, TLA:GNC, file:src/lib/litegraph/src/LGraphNode.ts (use "genhtml --ignore-errors unmapped ..." to bypass this error) ``` This happens when LCOV data references lines that don't map to source (from V8 coverage instrumentation). ## Changes 1. **Add `unmapped` to ignore-errors** — `--ignore-errors source,unmapped` handles both missing source files and unmapped line data 2. **Remove unreachable placeholder block** — The `if [ ! -s coverage/playwright/coverage.lcov ]` check is dead code because the step is already gated on `has-coverage == 'true'`, which only triggers when the merged LCOV exists and is non-empty ## Test plan - [ ] Verify workflow completes successfully on next push to main - [ ] Verify https://comfy-org.github.io/ComfyUI_frontend/ returns 200 Fixes #12229 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12273-fix-ci-add-unmapped-to-genhtml-ignore-errors-remove-unreachable-placeholder-3606d73d36508198a4ffddfce3354d4a) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
06e09df673 |
test: replace jobs mock fixture with typed route mocks (#12267)
## Summary Replace the merged stateful jobs API browser mock fixture with a small declarative typed route-mock foundation. ## Changes - **What**: Removes `JobsApiMock`, `jobsApiMockFixture`, and the old shared `jobFixtures` helper. - **What**: Adds a generic `RouteMocker` primitive for explicit typed JSON route responses. - **What**: Adds `jobsRouteFixture`, which registers explicit `/api/jobs` list/detail responses without filtering, mutation handling, or hidden in-memory backend behavior. - **What**: Migrates the current queue overlay and missing-media runtime specs onto the new jobs route fixture. - **What**: Keeps `./browser_tests/tsconfig.json` in the ESLint TypeScript resolver config. - **Dependencies**: None. ## Review Focus This is intended to be the foundation PR for the test-strategy reset: old stateful helper out, typed declarative route mocks in. It intentionally does not add the full asset sidebar, job history sidebar, or floating QPO coverage suite; those should stack on top after this fixture shape is accepted. The boundary this PR is trying to preserve: route mocks may describe frontend-visible API responses, but should not implement Core queue/history mutation semantics. Context: https://www.notion.so/comfy-org/E2E-Test-Strategy-for-Assets-Job-History-and-Queue-Progress-Overlay-35f6d73d365081209bc5f10e6c7eb8de ## Screenshots (if applicable) Not applicable. |
||
|
|
e972d658d3 |
feat(settings): lower Comfy.Pointer.ClickBufferTime default from 150ms to 32ms (#12032)
*PR Created by the Glary-Bot Agent* --- ## Summary Click vs drag is disambiguated by two thresholds: distance (`Comfy.Pointer.ClickDrift`, 6px) and time (`Comfy.Pointer.ClickBufferTime`). Distance does the real work — any intentional drag immediately exceeds 6px on the first move. The time threshold only matters in the corner case where the pointer is held still then released without crossing the distance threshold. The previous 150ms default forces every pointerdown to wait up to 150ms before drag begins, even when the user is clearly dragging. This is visible as lag when click+dragging an unselected node, where the wait stacks on top of selection-state work that runs at drag start ([FE-558](https://linear.app/comfyorg/issue/FE-558/bug-delay-when-clickdragging-an-unselected-node)). 32ms (~2 frames at 60fps) is well below human-perceptible click latency and still safely above incidental jitter on pointerdown. ## Changes - `src/platform/settings/constants/coreSettings.ts` — `Comfy.Pointer.ClickBufferTime`: - `defaultValue: 150` → `32` - `versionAdded: '1.4.3'` retained, add `versionModified: '1.44.19'` - Slider `step: 25` → `1` (the old step made `32` unrepresentable on the slider) - Tooltip extended with rationale (why distance is the real disambiguator, why this should be small) - `src/lib/litegraph/src/CanvasPointer.ts` — Static `bufferTime = 150` → `32` for consistency with the user-setting default; the runtime `watchEffect` in `useLitegraphSettings.ts` continues to override at boot. JSDoc explains the rationale and that the user setting overrides at runtime. ## Verification - `pnpm typecheck` clean. - Litegraph + settings test suites: 998/998 pass. - `pnpm format` clean (lint-staged enforced on commit). ## Why not just leave it user-overridable? It already is. But the default is what every user — including ones who never open settings — experiences. 150ms was never a justified value (no comment explains it, no benchmark, and 6px distance covers the disambiguation). Defaults should reflect best UX. Refs FE-558. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12032-feat-settings-lower-Comfy-Pointer-ClickBufferTime-default-from-150ms-to-32ms-3586d73d365081f5a29cdc84b4b736e3) 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> |
||
|
|
f090ea3d28 |
fix: preserve app builder inputs through graph reconfiguration (#11422)
*PR Created by the Glary-Bot Agent* --- ## Summary - Fixes app mode bug where custom combo inputs (and other widget inputs) unselect and show "Widget not visible" after refreshing or reopening a saved app workflow - Root cause: `resetSelectedToWorkflow()` loads from `changeTracker.activeState` which may not have linearData yet after refresh, and `pruneLinearData()` prunes valid entries during graph loading when nodes aren't yet in the graph - Two defensive guards: fallback to `initialState` for authoritative data, and skip pruning during graph loading ## Changes - `appModeStore.ts`: `resetSelectedToWorkflow()` now falls back to `initialState.extra.linearData` when `activeState` has none - `appModeStore.ts`: `pruneLinearData()` skips node-existence checks when `ChangeTracker.isLoadingGraph` is true - Unit tests: 4 new tests covering both fix paths (pruning during loading, fallback to initialState) - E2E test: Save-as → close → reopen → verify all inputs persist with no "Widget not visible" ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11422-fix-preserve-app-builder-inputs-through-graph-reconfiguration-3476d73d36508166a563f7df3967665c) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |
||
|
|
daab936d15 |
feat: add Cloud Status link to website footer (#12237)
*PR Created by the Glary-Bot Agent* --- ## Summary Adds a "Cloud Status" link to the Contact column of the website footer, pointing to https://status.comfy.org so users can discover the service status page from any page on the marketing site. ## Changes - **What**: New external link in the Contact column of `SiteFooter.vue`, plus matching i18n keys (`footer.cloudStatus` in `en`/`zh-CN`) and a `cloudStatus` entry in the `externalLinks` config. ## Review Focus - Placement: Slotted between Support and Press in the Contact column (alongside the other support/operational links). Open to moving to the Resources column instead if preferred. - URL: `https://status.comfy.org` — assumed convention; swap if a different status page URL is preferred. - Also tightened the `contactColumn` type annotation to `{ title: string; links: FooterLink[] }` to match `companyColumn` / `topColumns` so the `external: true` field is properly typed. ## Screenshots Desktop (Contact column, right side):  Mobile (2×2 grid, Contact column bottom-right):  ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12237-feat-add-Cloud-Status-link-to-website-footer-3606d73d36508190a906fdde6d86706d) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
f176d18fe0 |
fix(website): refresh cloud nodes snapshot in release workflow + strict production builds (#12219)
*PR Created by the Glary-Bot Agent* --- ## Summary `Release: Website` only refreshed the Ashby snapshot, so the cloud-nodes snapshot (`apps/website/src/data/cloud-nodes.snapshot.json`) was stale on every release. `loadPacksForBuild()` then silently fell back to that snapshot because `WEBSITE_CLOUD_API_KEY` was never plumbed through CI or Vercel, leaving production at `/cloud/supported-nodes` with placeholder data (e.g. `rgthree-comfy` listed as supported when it isn't — visible at line 104 of the committed snapshot, last fetched 2026-05-04). ## Changes - **New composite action `.github/actions/cloud-nodes-pull`** mirroring `ashby-pull`: runs `pnpm --filter @comfyorg/website cloud-nodes:refresh-snapshot` with `WEBSITE_CLOUD_API_KEY`. The script already `process.exit(1)`s on any non-`fresh` outcome, so refresh failures are loud. - **`release-website.yaml`** now runs both refreshes and opens a single PR with both updated snapshots. Renamed the job to `refresh-snapshots`, updated branch/commit/title/body for the wider scope, and kept the existing `Release:Website` label so downstream automation is unaffected. - **`cloudNodes.build.ts`** throws when the outcome is `'stale'` **and** `VERCEL_ENV === 'production'`. Preview / local builds keep the snapshot fallback so contributors without key access are unaffected. The CI reporter still runs first so the GitHub annotation explaining *why* it's stale is visible in the failed job. - **`ci-vercel-website-preview.yaml`**: passes `WEBSITE_CLOUD_API_KEY` to `vercel build` in both preview and production jobs, and adds a preflight step on `deploy-production` that hard-fails before `vercel build --prod` if the secret is missing — surfacing config drift with a maintainer-friendly error annotation instead of mid-build. - **`apps/website/README.md`**: documents the production-strictness behavior, the new required secret (GitHub Actions + Vercel env), and the manual refresh path. - **New unit tests** in `cloudNodes.build.test.ts` (6 cases): fresh, stale-no-VERCEL_ENV, stale-on-preview, stale-on-production, failed-regardless, and "still reports on stale-in-production before throwing". ## Manual / one-time steps required before merging This PR cannot finish the job alone. A maintainer must also: 1. Add `WEBSITE_CLOUD_API_KEY` as a **GitHub Actions repo secret** in `Comfy-Org/ComfyUI_frontend`. 2. Add `WEBSITE_CLOUD_API_KEY` to the **Vercel project environment** (`production` env at minimum; `preview` recommended). 3. Investigate why `rgthree-comfy` is in the current snapshot — either the Cloud API was actually returning it on 2026-05-04, the snapshot was generated against a non-production environment, or it was hand-edited. The first manual run of `Release: Website` after this PR merges will confirm. Without step 1, the new `Release: Website` job will fail loudly (the refresh script exits 1 with `missing WEBSITE_CLOUD_API_KEY`). Without step 2, the new preflight will fail the production deploy with a clear error annotation pointing at `apps/website/README.md`. Both failure modes are intentional — they replace today's silent stale snapshot. ## Related (out of scope for this PR) The other half of the original report — production 404s on `/p/supported-models/*`, `/cloud/supported-nodes/*`, `/demos/community-workflows` from PRs #11892 / #11903 / #11942 — is a `comfy-router` allow-list gap (those paths exist in the Vercel build as pre-rendered static HTML). That fix needs to land in `Comfy-Org/comfy-router` and is being handled separately since glary doesn't have access to that repo. ## Verification - `pnpm --filter @comfyorg/website test:unit` — 75/75 pass (6 new in `cloudNodes.build.test.ts`) - `pnpm --filter @comfyorg/website typecheck` — 0 errors, 0 warnings (2 pre-existing hints unrelated to this PR) - `pnpm format` + `pnpm exec eslint` on changed files — clean - `js-yaml` validates `release-website.yaml`, `cloud-nodes-pull/action.yaml`, `ci-vercel-website-preview.yaml` - Oracle code review (round 1) raised 1 warning + 1 suggestion; both addressed in commit 2. **Manual verification not applicable**: the runtime changes are GitHub Actions workflows and a Vercel-env-gated branch in a build-time module — they cannot meaningfully run outside of GitHub Actions / Vercel, and the strict-on-stale path is exhaustively covered by the 6 unit tests (including the exact assertions a manual run would check: throws on `VERCEL_ENV=production` + stale, passes on preview, reports observability annotation before throwing). The end-to-end behavior will be verified by the first `Release: Website` dispatch and the next production deploy after the maintainer adds the secret. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12219-fix-website-refresh-cloud-nodes-snapshot-in-release-workflow-strict-production-build-35f6d73d3650816d8f32d403cb39d733) by [Unito](https://www.unito.io) --------- Co-authored-by: glary <bot@glary.dev> |
||
|
|
01742672bb |
feat: extend version warning to all comfy_package_versions entries (#12167)
*PR Created by the Glary-Bot Agent* --- ## Summary Extend the existing frontend version-mismatch warning UI to consume the new `comfy_package_versions` array now exposed by ComfyUI's `/system_stats` endpoint. The backend ships installed/required versions for every `comfy*` package pinned in `requirements.txt` (frontend, workflow-templates, embedded-docs, kitchen, aimdo, …); the frontend now surfaces one toast per outdated package, reusing the existing N=1 frontend-version warning shape. Backend PR: https://github.com/Comfy-Org/ComfyUI/pull/13875 ## Changes - `src/schemas/apiSchema.ts` — add `comfy_package_versions: Array<{name, installed, required}>` to the `SystemStats` schema and export a `ComfyPackageVersion` type. The field is `.optional()` so older backends remain compatible. - `src/platform/updates/common/versionCompatibilityStore.ts` — new `outdatedComfyPackages` / `packageWarningMessages` computeds that mirror the existing semver `gt` comparison (same `valid` guard). Skip `comfyui-frontend-package` because the dedicated frontend warning above already covers it (and uses the running bundle's version rather than the installed pip version). Outdated packages are sorted by `name`/`installed`/`required` before being folded into the dismissal storage key so the key is stable across response orderings — a fresh package bump re-shows the warning, but the same outdated set in a different order does not. - `src/platform/updates/common/useFrontendVersionMismatchWarning.ts` — emit one toast per outdated package in addition to the existing frontend toast, reusing the same i18n wrapper and the existing `hasShownWarning` once-per-session guard. - `src/locales/en/main.json` — new `g.comfyPackageOutdated` string. - Unit tests — added coverage for outdated/skip/invalid-version paths, dismissal-key inclusion, and stable ordering. Existing 21 store + 8 composable tests untouched and still passing. CI suppression is unchanged: warnings still gate on `versionCompatibilityStore.shouldShowWarning` (which respects the `Comfy.VersionCompatibility.DisableWarnings` setting and the 7-day dismissal cache), and unit tests continue to mock the store the same way. ## Verification - `pnpm vitest run` for the version-warning module: **31/31** passing. - Targeted sweep across `src/platform/updates`, `src/stores/systemStatsStore.test.ts`, `src/schemas`: **160/160** passing. - `pnpm typecheck`: clean. - `pnpm lint`: 0 errors (3 pre-existing warnings in unrelated 3D test files). - `pnpm format`: applied, no incidental changes. - **Manual Playwright run** against the real dev server with `/api/system_stats` intercepted to return outdated package data — produced exactly the expected toasts and correctly skipped `comfyui-frontend-package` and the up-to-date `comfy-kitchen` entry. Same run with all-up-to-date data produced zero toasts. ### Toasts produced (manual verification) The fixture used: `required_frontend_version=99.99.99`, plus `comfy_package_versions=[frontend-package (outdated, skipped), workflow-templates 0.9.0→0.9.5 (outdated), embedded-docs 0.4.0→0.5.0 (outdated), comfy-kitchen 0.2.8→0.2.8 (up to date)]`. ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12167-feat-extend-version-warning-to-all-comfy_package_versions-entries-35e6d73d365081e7b993d0f06c9e5c98) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
b7fe0365af |
fix: remove "400 Free Credits Monthly" line from cloud paywall modal (#12251)
*PR Created by the Glary-Bot Agent* --- ## Summary The in-app cloud paywall modal shown on first install of ComfyUI Desktop 1.0 advertised "400 Free Credits Monthly" as a benefit, but the free tier is currently disabled, making that copy misleading. Per the thread direction, this skips the feature-flag plumbing (which isn't available on Desktop anyway) and simply removes the line. ## Changes - `CloudNotificationContent.vue`: change the feature loop from `n in 4` to `n in [2, 3, 4]` so feature1 is skipped. - `src/locales/*/main.json` (12 locales): remove the now-unused `cloudNotification.feature1Title` key. - `browser_tests/tests/dialog.spec.ts`: add a regression assertion that "400" / "Free Credits" copy is no longer present in the modal. ## Test plan - `pnpm typecheck` — passes - `pnpm typecheck:browser` — passes - `pnpm exec vitest run src/platform/cloud/notification/components/DesktopCloudNotificationController.test.ts` — 3/3 pass - Pre-commit hooks (oxfmt, oxlint, eslint, stylelint, typecheck) — all pass - Manual: triggered `showCloudNotification()` against the dev server and confirmed only 3 benefit rows render (screenshot attached); the "400 Free Credits Monthly" line is gone. - Fixes DESK2-90 ## Screenshots  ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12251-fix-remove-400-Free-Credits-Monthly-line-from-cloud-paywall-modal-3606d73d365081eaa572e6cd995278d8) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
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) |