feat: surface missing models in Error Tab for OSS and remove legacy dialog#9921
feat: surface missing models in Error Tab for OSS and remove legacy dialog#9921
Conversation
- Remove isCloud guard from scanAllModelCandidates and surfaceMissingModels so OSS users see missing models in the Error Tab - Add Download button for OSS models with file size display - Group non-asset-supported models by directory instead of UNSUPPORTED in OSS - Move download business logic from dialog/content to platform/missingModel - Add folderPaths/fileSizes to missingModelStore with setter methods - Add race condition guard for fileSizes fetch - Add OSS grouping and display tests
- Move download business logic to platform/missingModel/missingModelDownload - Fix desktop download by awaiting folderPaths before surfacing models - Remove fragile reference-equality guard for fileSizes fetch - Add store setter methods for folderPaths and fileSizes - Add aria-label with model name to Download button - Add aria-expanded to expand/collapse toggle - Add warning icon for unknown category header (WCAG 1.4.1) - Remove collapseState change-detector test - Add Cloud environment contrast tests for missingModelGroups - Add cross-reference comments for extension constant lists
- Remove legacy dialog components and composable (MissingModelsContent, MissingModelsHeader, MissingModelsFooter, useMissingModelsDialog, missingModelsUtils) - Remove dialog invocation from workflowService - Add Download All button to missing model group header in Error Tab with total file size display - Add downloadAll i18n key under rightSidePanel.missingModels
- Rewrite Playwright tests to verify error overlay and Error Tab instead of legacy dialog title/buttons - Enable UseNewMenu and ShowErrorsTab settings in test setup - Remove API-mock-based model installation test (incompatible with OSS combo-options detection path) - Remove ShowMissingModelsWarning references from fixtures and templates
- Remove redundant text-warning-background class on inner span - Add error overlay visibility assertion for widget mismatch test - Fix hasValidDirectory to check for non-empty array - Improve test isolation with explicit mockIsCloud reset - Extract makeModel helper to module level to reduce duplication - Add cross-reference comments for extension constants
🎨 Storybook: ✅ Built — View Storybook |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR removes the dialog-based missing-models UI and composable, migrates missing-model reporting into the right-side error panel, adds per-URL size tracking and a "download all" flow, unifies cloud/non-cloud candidate scanning and metadata fetch, and updates tests and store state for folder paths and file sizes. Changes
Sequence DiagramsequenceDiagram
participant App as App Init
participant Scanner as Candidate Scanner
participant Store as MissingModelStore
participant Cloud as Cloud Detector
participant Verifier as Asset Verifier
participant Panel as Right-side Error Panel
participant Downloader as Download Handler
rect rgba(180,100,100,0.5)
Note over App,Panel: Legacy dialog flow removed
App->>Panel: (old) show missing-models dialog
end
rect rgba(100,180,120,0.5)
Note over App,Scanner: New unified scan & surface flow
App->>Scanner: scanAllModelCandidates(getDirectory)
Scanner->>Store: add/update candidate entries
App->>Cloud: isCloud?
alt Cloud
App->>Verifier: verify asset availability (asset-based filter)
Verifier-->>Store: asset verification results
else Non-Cloud
App->>Store: setFolderPaths(folderPaths)
Scanner->>Store: fetch per-model metadata (sizes) -> setFileSize(url,size)
end
App->>Panel: render error-overlay with grouped missing models
Panel->>Downloader: user triggers "Download all"
Downloader->>Store: read downloadable models & sizes
Downloader->>Downloader: invoke per-model downloadModel calls
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎭 Playwright: ✅ 556 passed, 0 failed · 5 flaky📊 Browser Reports
|
📦 Bundle: 4.99 MB gzip 🔴 +2.11 kBDetailsSummary
Category Glance App Entry Points — 21.9 kB (baseline 21.7 kB) • 🔴 +192 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.09 MB (baseline 1.08 MB) • 🔴 +3.77 kBGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 75.7 kB (baseline 75.3 kB) • 🔴 +486 BTop-level views, pages, and routed surfaces
Status: 11 added / 11 removed Panels & Settings — 460 kB (baseline 460 kB) • 🔴 +681 BConfiguration panels, inspectors, and settings screens
Status: 22 added / 22 removed User & Accounts — 16.8 kB (baseline 16.6 kB) • 🔴 +162 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed Editors & Dialogs — 81.9 kB (baseline 81.8 kB) • 🔴 +170 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 59.2 kB (baseline 59 kB) • 🔴 +243 BReusable component library chunks
Status: 13 added / 13 removed Data & Services — 3.14 MB (baseline 3.17 MB) • 🟢 -27.8 kBStores, services, APIs, and repositories
Status: 17 added / 16 removed Utilities & Hooks — 84.2 kB (baseline 68.9 kB) • 🔴 +15.4 kBHelpers, composables, and utility bundles
Status: 19 added / 18 removed Vendor & Third-Party — 9.78 MB (baseline 9.78 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Status: 6 added / 6 removed Other — 8.21 MB (baseline 8.21 MB) • 🔴 +7.73 kBBundles that do not match a named category
Status: 123 added / 121 removed |
⚡ Performance Report
All metrics
Historical variance (last 3 runs)
Raw data{
"timestamp": "2026-03-15T13:20:55.196Z",
"gitSha": "46cc53faca6d8690af343ac892d5cb05924be4ea",
"branch": "feat/oss-missing-model-error-tab",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2047.7379999999812,
"styleRecalcs": 11,
"styleRecalcDurationMs": 12.722999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 503.37600000000003,
"heapDeltaBytes": 2370060,
"domNodes": 25,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 36.202,
"eventListeners": 30
},
{
"name": "canvas-idle",
"durationMs": 2012.5430000000506,
"styleRecalcs": 12,
"styleRecalcDurationMs": 15.408000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 477.567,
"heapDeltaBytes": 1801928,
"domNodes": 27,
"jsHeapTotalBytes": 18612224,
"scriptDurationMs": 33.29699999999999,
"eventListeners": 30
},
{
"name": "canvas-idle",
"durationMs": 2033.7359999998625,
"styleRecalcs": 12,
"styleRecalcDurationMs": 12.768999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 415.36500000000007,
"heapDeltaBytes": 1410252,
"domNodes": 25,
"jsHeapTotalBytes": 18087936,
"scriptDurationMs": 26.454000000000004,
"eventListeners": 30
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1945.3459999999723,
"styleRecalcs": 80,
"styleRecalcDurationMs": 45.666,
"layouts": 12,
"layoutDurationMs": 3.5320000000000005,
"taskDurationMs": 932.298,
"heapDeltaBytes": -2716204,
"domNodes": 67,
"jsHeapTotalBytes": 18874368,
"scriptDurationMs": 143.686,
"eventListeners": 30
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2069.3549999998595,
"styleRecalcs": 87,
"styleRecalcDurationMs": 52.306000000000004,
"layouts": 12,
"layoutDurationMs": 3.8859999999999997,
"taskDurationMs": 1134.686,
"heapDeltaBytes": -3205572,
"domNodes": 71,
"jsHeapTotalBytes": 18612224,
"scriptDurationMs": 153.335,
"eventListeners": 30
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1863.0000000000564,
"styleRecalcs": 75,
"styleRecalcDurationMs": 45.709,
"layouts": 12,
"layoutDurationMs": 3.497,
"taskDurationMs": 828.4540000000001,
"heapDeltaBytes": -2579768,
"domNodes": 62,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 138.412,
"eventListeners": 30
},
{
"name": "dom-widget-clipping",
"durationMs": 623.5490000001391,
"styleRecalcs": 17,
"styleRecalcDurationMs": 16.235,
"layouts": 1,
"layoutDurationMs": 0.2710000000000001,
"taskDurationMs": 400.53299999999996,
"heapDeltaBytes": 7525128,
"domNodes": 71,
"jsHeapTotalBytes": 13631488,
"scriptDurationMs": 69.904,
"eventListeners": 47
},
{
"name": "dom-widget-clipping",
"durationMs": 679.649000000154,
"styleRecalcs": 13,
"styleRecalcDurationMs": 12.373,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 446.168,
"heapDeltaBytes": 13355436,
"domNodes": 25,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 83.88299999999998,
"eventListeners": 26
},
{
"name": "dom-widget-clipping",
"durationMs": 598.9519999998265,
"styleRecalcs": 14,
"styleRecalcDurationMs": 12.177999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 381.81899999999996,
"heapDeltaBytes": 13325160,
"domNodes": 25,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 71.49799999999999,
"eventListeners": 26
},
{
"name": "large-graph-idle",
"durationMs": 2028.137999999899,
"styleRecalcs": 15,
"styleRecalcDurationMs": 18.668,
"layouts": 1,
"layoutDurationMs": 0.21199999999999994,
"taskDurationMs": 697.9000000000001,
"heapDeltaBytes": -8616708,
"domNodes": 74,
"jsHeapTotalBytes": 8888320,
"scriptDurationMs": 126.08900000000001,
"eventListeners": 49
},
{
"name": "large-graph-idle",
"durationMs": 2017.9829999999583,
"styleRecalcs": 12,
"styleRecalcDurationMs": 14.386,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 620.24,
"heapDeltaBytes": -10109388,
"domNodes": 26,
"jsHeapTotalBytes": 8245248,
"scriptDurationMs": 119.119,
"eventListeners": 34
},
{
"name": "large-graph-idle",
"durationMs": 2050.5820000000767,
"styleRecalcs": 15,
"styleRecalcDurationMs": 16.981,
"layouts": 1,
"layoutDurationMs": 0.196,
"taskDurationMs": 689.256,
"heapDeltaBytes": -8091588,
"domNodes": 72,
"jsHeapTotalBytes": 9699328,
"scriptDurationMs": 129.53400000000002,
"eventListeners": 49
},
{
"name": "large-graph-pan",
"durationMs": 2146.4359999999942,
"styleRecalcs": 69,
"styleRecalcDurationMs": 18.214000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1246.143,
"heapDeltaBytes": 2700320,
"domNodes": 16,
"jsHeapTotalBytes": 9236480,
"scriptDurationMs": 448.54499999999996,
"eventListeners": 4
},
{
"name": "large-graph-pan",
"durationMs": 2129.9169999999776,
"styleRecalcs": 71,
"styleRecalcDurationMs": 20.535999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1196.4759999999999,
"heapDeltaBytes": 2364704,
"domNodes": 22,
"jsHeapTotalBytes": 10280960,
"scriptDurationMs": 448.78499999999997,
"eventListeners": 4
},
{
"name": "large-graph-pan",
"durationMs": 2110.6979999999567,
"styleRecalcs": 70,
"styleRecalcDurationMs": 18.083999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1164.089,
"heapDeltaBytes": 4016740,
"domNodes": 20,
"jsHeapTotalBytes": 8445952,
"scriptDurationMs": 427.295,
"eventListeners": 4
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 682.1600000000672,
"styleRecalcs": 48,
"styleRecalcDurationMs": 16.012999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 454.48699999999997,
"heapDeltaBytes": 12940676,
"domNodes": 25,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 143.6,
"eventListeners": 32
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 584.8790000000008,
"styleRecalcs": 49,
"styleRecalcDurationMs": 14.829000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 388.92199999999997,
"heapDeltaBytes": 13281668,
"domNodes": 26,
"jsHeapTotalBytes": 14942208,
"scriptDurationMs": 126.006,
"eventListeners": 36
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 606.7900000000463,
"styleRecalcs": 49,
"styleRecalcDurationMs": 15.270000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 406.06299999999993,
"heapDeltaBytes": 12312308,
"domNodes": 25,
"jsHeapTotalBytes": 18612224,
"scriptDurationMs": 130.56099999999998,
"eventListeners": 32
},
{
"name": "subgraph-idle",
"durationMs": 1996.4910000001055,
"styleRecalcs": 11,
"styleRecalcDurationMs": 12.469999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.616,
"heapDeltaBytes": 1156156,
"domNodes": 26,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 28.715999999999998,
"eventListeners": 32
},
{
"name": "subgraph-idle",
"durationMs": 2010.5849999999919,
"styleRecalcs": 13,
"styleRecalcDurationMs": 13.456999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 445.6789999999999,
"heapDeltaBytes": 1799048,
"domNodes": 26,
"jsHeapTotalBytes": 17301504,
"scriptDurationMs": 24.711000000000002,
"eventListeners": 30
},
{
"name": "subgraph-idle",
"durationMs": 1994.2419999999856,
"styleRecalcs": 11,
"styleRecalcDurationMs": 12.750000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 435.19699999999995,
"heapDeltaBytes": 1698992,
"domNodes": 25,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 24.332000000000008,
"eventListeners": 30
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1990.9880000000157,
"styleRecalcs": 86,
"styleRecalcDurationMs": 61.53600000000001,
"layouts": 16,
"layoutDurationMs": 4.874,
"taskDurationMs": 1011.151,
"heapDeltaBytes": -6563700,
"domNodes": 78,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 115.37400000000001,
"eventListeners": 30
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1711.9410000000244,
"styleRecalcs": 80,
"styleRecalcDurationMs": 42.438,
"layouts": 17,
"layoutDurationMs": 4.718,
"taskDurationMs": 711.09,
"heapDeltaBytes": 13846512,
"domNodes": 110,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 103.249,
"eventListeners": 49
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 2028.0480000001262,
"styleRecalcs": 85,
"styleRecalcDurationMs": 53.888000000000005,
"layouts": 16,
"layoutDurationMs": 5.269,
"taskDurationMs": 1004.8279999999999,
"heapDeltaBytes": -6867676,
"domNodes": 76,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 112.269,
"eventListeners": 30
}
]
} |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
src/platform/missingModel/missingModelDownload.ts (1)
125-128: Prefer strict numeric validation forcontent-length.
parseIntcan accept partially numeric header values. A stricter conversion plus non-negative check avoids propagating invalid file sizes.Suggested patch
- const parsedSize = size ? parseInt(size, 10) : null + const parsedSize = size === null ? null : Number(size) return { - fileSize: - parsedSize !== null && !Number.isNaN(parsedSize) ? parsedSize : null, + fileSize: + parsedSize !== null && + Number.isFinite(parsedSize) && + parsedSize >= 0 + ? parsedSize + : null, gatedRepoUrl: null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/platform/missingModel/missingModelDownload.ts` around lines 125 - 128, The code uses parseInt which accepts partial numeric strings; change the validation for the content-length header by first testing the raw size string with a strict numeric check (e.g., /^\d+$/) and only then converting to a Number; set parsedSize using Number(size) or parseInt(size,10) after the regex check and ensure you also verify Number.isInteger(parsedSize) && parsedSize >= 0 before returning fileSize (update the variables referenced: size, parsedSize and the returned fileSize).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@browser_tests/tests/dialog.spec.ts`:
- Around line 98-101: The cleanup fetch inside the test setup ignores the
response; change the comfyPage.page.evaluate call (the anonymous callback passed
to comfyPage.page.evaluate and the surrounding await) to await the fetch,
inspect the response (e.g., check response.ok or response.status), and propagate
a failure if cleanup did not succeed—return a boolean or error message from the
page evaluate and then assert or throw in the test so the test fails fast when
cleanup fails (referencing comfyPage.page.evaluate and comfyPage.url to locate
the call).
- Around line 108-109: Add the missing test id key and replace hardcoded
selectors: add error-overlay: 'error-overlay' as errorOverlay under the dialogs
section of the TestIds export in browser_tests/fixtures/selectors.ts, then
update all occurrences of the string selector '[data-testid="error-overlay"]' in
dialog.spec.ts and execution.spec.ts to use TestIds.dialogs.errorOverlay (e.g.,
comfyPage.page.locator(`[data-testid="${TestIds.dialogs.errorOverlay}"]`) or
however your locator is constructed in the project).
In `@src/platform/missingModel/components/MissingModelRow.vue`:
- Around line 136-151: The Download CTA currently relies only on the
`downloadable` check, which allows the button to appear in cloud builds or when
no real destination exists; update `MissingModelRow.vue` to gate the Download
button rendering and the `handleDownload` path on `!isCloud` and a real
destination path (e.g. `folderPaths[directory] && folderPaths[directory][0]`)
instead of just `downloadable`, ensure `downloadModel()` continues to receive a
valid folder path, and adjust `downloadLabel`/`handleDownload` as needed to
reflect the destination-aware state; then extract and reuse this same predicate
in `src/components/rightSidePanel/errors/TabErrors.vue` so the Download All
button uses identical logic and remains consistent across OSS/cloud/desktop
builds.
In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 100-102: The find predicate assumes file.downloadUrl is a string
and calls .startsWith on it; change the predicate used when constructing
matchingFile in missingModelDownload.ts to first guard that file.downloadUrl is
a non-empty string (e.g., typeof file.downloadUrl === "string" &&
file.downloadUrl) before calling .startsWith(url) so malformed or missing
downloadUrl values won't throw and block metadata resolution.
In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 540-541: The showPendingWarnings() implementation only extracts
missingNodeTypes from wf.pendingWarnings before clearing it, so update
showPendingWarnings() to also extract missingModels (e.g., const {
missingNodeTypes, missingModels } = wf.pendingWarnings) and then surface them
the same way node types are handled — either by calling
surfaceMissingModels(missingModels) (or the equivalent upstream helper) or by
logging/dispatching the missingModels warnings here — before setting
wf.pendingWarnings = null; ensure you reference the existing functions
runMissingModelPipeline and surfaceMissingModels if reusing that surfacing
logic.
In `@src/scripts/app.ts`:
- Around line 1424-1433: scanAllModelCandidates is called with a
directory/category resolver only in cloud builds, leaving the third argument
undefined for OSS so scanned candidates have directory: undefined; update the
call so the same node-type→category resolver used in cloud
(useModelToNodeStore().getCategoryForNodeType) is passed in OSS as well (i.e.
replace the undefined third argument with the resolver function), keeping the
existing assetService.shouldUseAssetBrowser usage for the second argument when
isCloud is true.
- Around line 1478-1536: Create and use the same abort-controller pattern in the
OSS (else) branch as the cloud branch: call
missingModelStore.createVerificationAbortController() to get a controller,
pass/observe controller.signal in the async flows started in the else branch
(the api.getFolderPaths() promise chain and the Promise.allSettled(...) that
imports fetchModelMetadata), and before any state mutations
(missingModelStore.setFolderPaths,
useExecutionErrorStore().surfaceMissingModels, missingModelStore.setFileSize)
check controller.signal.aborted and skip updates if aborted; if
fetchModelMetadata supports a signal, pass controller.signal into it, otherwise
check controller.signal.aborted immediately after each await and before calling
setFileSize. Ensure the controller is created in the same scope as
enrichedCandidates handling so stale workflows can be aborted like in
verifyAssetSupportedCandidates.
---
Nitpick comments:
In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 125-128: The code uses parseInt which accepts partial numeric
strings; change the validation for the content-length header by first testing
the raw size string with a strict numeric check (e.g., /^\d+$/) and only then
converting to a Number; set parsedSize using Number(size) or parseInt(size,10)
after the regex check and ensure you also verify Number.isInteger(parsedSize) &&
parsedSize >= 0 before returning fileSize (update the variables referenced:
size, parsedSize and the returned fileSize).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 569c3d88-6809-4211-b8dc-bb5f9e0b86fd
📒 Files selected for processing (22)
browser_tests/fixtures/ComfyPage.tsbrowser_tests/tests/dialog.spec.tsbrowser_tests/tests/templates.spec.tssrc/components/dialog/content/MissingModelsContent.vuesrc/components/dialog/content/MissingModelsFooter.vuesrc/components/dialog/content/MissingModelsHeader.vuesrc/components/dialog/content/missingModelsUtils.test.tssrc/components/rightSidePanel/errors/TabErrors.vuesrc/components/rightSidePanel/errors/useErrorGroups.test.tssrc/components/rightSidePanel/errors/useErrorGroups.tssrc/composables/useMissingModelsDialog.tssrc/locales/en/main.jsonsrc/platform/missingModel/components/MissingModelCard.test.tssrc/platform/missingModel/components/MissingModelCard.vuesrc/platform/missingModel/components/MissingModelRow.vuesrc/platform/missingModel/components/MissingModelUrlInput.vuesrc/platform/missingModel/missingModelDownload.tssrc/platform/missingModel/missingModelScan.tssrc/platform/missingModel/missingModelStore.tssrc/platform/workflow/core/services/workflowService.test.tssrc/platform/workflow/core/services/workflowService.tssrc/scripts/app.ts
💤 Files with no reviewable changes (6)
- browser_tests/tests/templates.spec.ts
- src/components/dialog/content/MissingModelsContent.vue
- src/components/dialog/content/MissingModelsFooter.vue
- src/composables/useMissingModelsDialog.ts
- src/components/dialog/content/missingModelsUtils.test.ts
- src/components/dialog/content/MissingModelsHeader.vue
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/platform/missingModel/missingModelDownload.ts (1)
99-102:⚠️ Potential issue | 🟡 MinorThe guard order is still incorrect.
The past review correctly identified this issue. The current code still calls
.startsWith(url)before validating thatdownloadUrlexists. The logical AND short-circuits left-to-right, sofile.downloadUrl.startsWith(url)executes first and will throw ifdownloadUrlisundefinedornull.Suggested fix
const matchingFile = data.files?.find( - (file) => file.downloadUrl.startsWith(url) && file.downloadUrl + (file) => file.downloadUrl && file.downloadUrl.startsWith(url) )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/platform/missingModel/missingModelDownload.ts` around lines 99 - 102, The predicate passed to data.files?.find has the guard order reversed and can call file.downloadUrl.startsWith when downloadUrl is null/undefined; change the condition in the find to first check that file.downloadUrl is truthy (e.g., file.downloadUrl && file.downloadUrl.startsWith(url)) so startsWith is only invoked when downloadUrl exists (this affects the matchingFile lookup used to compute fileSize).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 99-102: The predicate passed to data.files?.find has the guard
order reversed and can call file.downloadUrl.startsWith when downloadUrl is
null/undefined; change the condition in the find to first check that
file.downloadUrl is truthy (e.g., file.downloadUrl &&
file.downloadUrl.startsWith(url)) so startsWith is only invoked when downloadUrl
exists (this affects the matchingFile lookup used to compute fileSize).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a21c522d-d6cf-4217-bb70-8690658a91c1
📒 Files selected for processing (1)
src/platform/missingModel/missingModelDownload.ts
- Assert cleanup response in E2E test beforeEach - Guard downloadUrl type in fetchCivitaiMetadata predicate - Pass directory resolver unconditionally for OSS model categorization - Add abort controller to OSS branch for stale workflow protection
7ca2ef0 to
320a836
Compare
|
Updating Playwright Expectations |
The tests covering fetchModelMetadata (HEAD/Civitai metadata fetching, caching, request deduplication, gated repo detection) were inadvertently deleted when the legacy missing models dialog was removed. The underlying logic lives in missingModelDownload.ts — port the tests there. Also add a clarifying comment to reconcileNodeErrorFlags explaining why the ShowErrorsTab guard only affects legacy nodes.
Add a "Copy URL" button in the missing model row header for non-asset-supported models in OSS environments. Also guard the Download button with !isCloud to ensure neither button appears in Cloud environments. - Fixes #9921
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
…ialog (#9921) ## Summary - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable ## Changes - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category ## Test plan - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` ## Screenshots https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com>
## Summary Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. ## Changes - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. ## Review Focus Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) ## Summary - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after #9459 ## Problem PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. ## Fix Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) ## Summary Fixes text field becoming non-editable when a previously linked input is removed from a custom node. ## Problem When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. ## Solution In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. ## Acceptance Criteria 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability ## Testing - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) ## Summary Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. ## Changes - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. ## Review Focus Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) ## What Add equality check before updating `appScalePercentage` reactive ref. ## Why Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. ## How Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. ## Perf Impact Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) ## Summary Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. ## Test - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears ## Related - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) ## Summary Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. ## New Metrics | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | ## Changes ### `browser_tests/fixtures/helpers/PerformanceHelper.ts` - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` ### `scripts/perf-report.ts` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis ## Why These 4 From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. ## Backward Compatibility The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) ## Summary - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 ### AS IS https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 ### TO BE https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) ## Summary Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. ## Changes - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. ## Review Focus The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) ## What Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. ## Why Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. ## Change In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` ## Gated on - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) ## Summary Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). ## Changes ### 1. Fix z-score baseline variance collection (`0/5 runs`) **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) ### 2. Fix force-push comment staleness **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. ### 3. Add permissions for baseline operations - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) ## One-time setup required After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). ## Testing - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) ## Summary - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ## Structure ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` ## Test Plan - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) ## What Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). ## Why Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. ## Tests Added - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation ## Workflow Asset `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. ## Verification - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) ## Summary - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files ## CI automation The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 ## How endpoint filtering works Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` ## Follow-up: replace manual types with generated ones This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | ## Test plan - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) ## Summary - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable ## Changes - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category ## Test plan - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` ## Screenshots https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) ## Summary Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. ## Changes - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  ## Review Focus In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) ## Summary Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. ## Changes ### New Metrics - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. ### New Test Scenarios | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | ### Infrastructure - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture ## Review Focus - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) ## Summary Fix group paste position: groups now paste at the cursor location instead of on top of the original. ## Changes - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. ## Screenshots Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) ## Summary Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. ## Changes - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> ## Review Focus The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) ## Summary Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. ## Changes - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation ## Review Focus Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) ## Summary Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. ## Changes - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> ## Review Focus The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) ## Summary Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 ## Red-Green Verification | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | ## manual testing ### as is https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 ### to be https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 ## Test Plan - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) ## Summary - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity ## Test plan - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes ## E2E test rationale Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) ## Summary Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. ## Changes - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior ## Review Focus - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) ## Problem SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. ## Root Cause The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. ## Fix Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. ## Testing - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) ## Summary The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) ## Summary Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. ## Changes - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` ## Review Focus - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. ## Stack This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import.  In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) 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> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
Summary
components/dialog/contenttoplatform/missingModelChanges
isCloudguard fromscanAllModelCandidatesandsurfaceMissingModelsso OSS detects missing modelsfolderPaths/fileSizesstate with setter methods, race condition guardMissingModelsContent,MissingModelsHeader,MissingModelsFooter,useMissingModelsDialog,missingModelsUtilsaria-labelwith model name,aria-expandedon toggle, warning icon for unknown categoryTest plan
pnpm test:browser:local -- --grep "Missing models in Error Tab"Screenshots
2026-03-15.012443.mp4
┆Issue is synchronized with this Notion page by Unito