Skip to content

fix: block missing e2e regression coverage in CodeRabbit#9987

Merged
christian-byrne merged 3 commits intomainfrom
bl/coderabbit-block-e2e-check
Mar 16, 2026
Merged

fix: block missing e2e regression coverage in CodeRabbit#9987
christian-byrne merged 3 commits intomainfrom
bl/coderabbit-block-e2e-check

Conversation

@benceruleanlu
Copy link
Member

@benceruleanlu benceruleanlu commented Mar 16, 2026

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.

@benceruleanlu benceruleanlu requested a review from a team as a code owner March 16, 2026 00:29
@dosubot dosubot bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Mar 16, 2026
@github-actions
Copy link

github-actions bot commented Mar 16, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/16/2026, 01:13:45 AM UTC

Links

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

🎭 Playwright: ✅ 559 passed, 0 failed · 4 flaky

📊 Browser Reports
  • chromium: View Report (✅ 546 / ❌ 0 / ⚠️ 4 / ⏭️ 10)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 10 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Warning

Ignoring CodeRabbit configuration file changes. For security, only the configuration from the base branch is applied for open source repositories.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c27c338d-25ba-4dbf-a7cf-e5e986c50c81

📥 Commits

Reviewing files that changed from the base of the PR and between 04f9dfe and c2d21d6.

📒 Files selected for processing (1)
  • .coderabbit.yaml

📝 Walkthrough

Walkthrough

Updated CodeRabbit configuration: added request_changes_workflow: true under reviews, set override_requested_reviewers_only: true under pre_merge_checks, changed the "End-to-end regression coverage for fixes" pre-merge check from mode: warning to mode: error, and expanded the check's instructions block to require relying solely on PR metadata (no shell commands or diff inspection) and to mark results as inconclusive if data is unavailable.

Changes

Cohort / File(s) Summary
Configuration
./.coderabbit.yaml
Added request_changes_workflow: true under reviews; added override_requested_reviewers_only: true under pre_merge_checks; changed End-to-end regression coverage for fixes pre-merge check from mode: warning to mode: error; added/expanded instructions guidance to rely only on PR metadata, avoid running shell commands or inspecting diffs, and mark inconclusive if data is unavailable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I hopped into config, nibbling a line,
Flags set to guard the merge-sign,
“No shells, no diffs,” I softly declare,
If data's gone, mark it unsure with care,
A joyful twitch—my whiskers align.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: block missing e2e regression coverage in CodeRabbit' clearly summarizes the main change: making CodeRabbit enforce e2e regression coverage checks as blockers for fix-like PRs.
Description check ✅ Passed The PR description covers all required template sections with clear detail: a concise summary, detailed changes explaining the configuration modifications, and a review focus statement addressing validation needs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
End-To-End Regression Coverage For Fixes ✅ Passed The PR satisfies criterion 2 by changing multiple files under browser_tests/ directory, including test files, fixtures, and documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bl/coderabbit-block-e2e-check
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

📦 Bundle: 4.99 MB gzip 🔴 +9 B

Details

Summary

  • Raw size: 23 MB baseline 23 MB — ⚪ 0 B
  • Gzip: 4.99 MB baseline 4.99 MB — 🔴 +9 B
  • Brotli: 3.85 MB baseline 3.85 MB — 🔴 +140 B
  • Bundles: 239 current • 239 baseline • 112 added / 112 removed

Category Glance
Panels & Settings 🟢 -12 kB (448 kB) · Views & Navigation 🔴 +12 kB (87.6 kB) · Vendor & Third-Party ⚪ 0 B (9.78 MB) · Other ⚪ 0 B (8.22 MB) · Data & Services ⚪ 0 B (3.14 MB) · Graph Workspace ⚪ 0 B (1.09 MB) · + 5 more

App Entry Points — 21.9 kB (baseline 21.9 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CW_W8b0H.js (new) 21.9 kB 🔴 +21.9 kB 🔴 +7.78 kB 🔴 +6.69 kB
assets/index-DukBqxLm.js (removed) 21.9 kB 🟢 -21.9 kB 🟢 -7.78 kB 🟢 -6.69 kB

Status: 1 added / 1 removed

Graph Workspace — 1.09 MB (baseline 1.09 MB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-paYBrLE9.js (removed) 1.09 MB 🟢 -1.09 MB 🟢 -231 kB 🟢 -174 kB
assets/GraphView-yxNulSmL.js (new) 1.09 MB 🔴 +1.09 MB 🔴 +231 kB 🔴 +175 kB

Status: 1 added / 1 removed

Views & Navigation — 87.6 kB (baseline 75.7 kB) • 🔴 +12 kB

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-B7KZzv9X.js (new) 15.6 kB 🔴 +15.6 kB 🔴 +3.38 kB 🔴 +2.89 kB
assets/CloudSurveyView-Dl3S4Gh0.js (removed) 15.6 kB 🟢 -15.6 kB 🟢 -3.38 kB 🟢 -2.88 kB
assets/AboutPanel-D_PAgE-y.js (new) 12 kB 🔴 +12 kB 🔴 +3.31 kB 🔴 +2.97 kB
assets/CloudLoginView-_gUrrVUQ.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -3.29 kB 🟢 -2.91 kB
assets/CloudLoginView-D-5ZJ1BD.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +3.29 kB 🔴 +2.91 kB
assets/CloudSignupView-CD4dfE5X.js (removed) 9.61 kB 🟢 -9.61 kB 🟢 -2.78 kB 🟢 -2.43 kB
assets/CloudSignupView-DSqEv3rm.js (new) 9.61 kB 🔴 +9.61 kB 🔴 +2.78 kB 🔴 +2.44 kB
assets/UserCheckView-Bq-y1Ksp.js (new) 9.01 kB 🔴 +9.01 kB 🔴 +2.31 kB 🔴 +2.01 kB
assets/UserCheckView-DhVYg1b_.js (removed) 9.01 kB 🟢 -9.01 kB 🟢 -2.31 kB 🟢 -2.02 kB
assets/CloudLayoutView-DjuJLEyl.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.29 kB 🟢 -1.98 kB
assets/CloudLayoutView-DseBL06e.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/CloudForgotPasswordView-BaDNNhnV.js (removed) 5.81 kB 🟢 -5.81 kB 🟢 -2.02 kB 🟢 -1.78 kB
assets/CloudForgotPasswordView-CA5CZCtn.js (new) 5.81 kB 🔴 +5.81 kB 🔴 +2.03 kB 🔴 +1.78 kB
assets/CloudAuthTimeoutView-CUky5g0h.js (new) 5.17 kB 🔴 +5.17 kB 🔴 +1.86 kB 🔴 +1.63 kB
assets/CloudAuthTimeoutView-Dp-eu7Ph.js (removed) 5.17 kB 🟢 -5.17 kB 🟢 -1.86 kB 🟢 -1.64 kB
assets/CloudSubscriptionRedirectView--Ads7jix.js (removed) 4.94 kB 🟢 -4.94 kB 🟢 -1.84 kB 🟢 -1.62 kB
assets/CloudSubscriptionRedirectView-DFpnwJPx.js (new) 4.94 kB 🔴 +4.94 kB 🔴 +1.84 kB 🔴 +1.63 kB
assets/UserSelectView-BH8NIPsN.js (removed) 4.67 kB 🟢 -4.67 kB 🟢 -1.73 kB 🟢 -1.53 kB
assets/UserSelectView-Ds_WS6Kg.js (new) 4.67 kB 🔴 +4.67 kB 🔴 +1.73 kB 🔴 +1.53 kB
assets/CloudSorryContactSupportView-fqeQ05j4.js 1.21 kB 1.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-D0Bu-Rt0.js 385 B 385 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 9 removed

Panels & Settings — 448 kB (baseline 460 kB) • 🟢 -12 kB

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-Ca_O03HY.js (new) 28.9 kB 🔴 +28.9 kB 🔴 +6.17 kB 🔴 +5.48 kB
assets/KeybindingPanel-Cuzw2p3V.js (removed) 28.9 kB 🟢 -28.9 kB 🟢 -6.17 kB 🟢 -5.49 kB
assets/SecretsPanel-CdiJPQp3.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.41 kB 🔴 +4.75 kB
assets/SecretsPanel-DyyOEGvj.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.41 kB 🟢 -4.76 kB
assets/LegacyCreditsPanel-BDklaglp.js (new) 21.3 kB 🔴 +21.3 kB 🔴 +5.69 kB 🔴 +5.01 kB
assets/LegacyCreditsPanel-BYvKjbst.js (removed) 21.3 kB 🟢 -21.3 kB 🟢 -5.69 kB 🟢 -5.01 kB
assets/SubscriptionPanel-7ZqKuXZs.js (new) 19.3 kB 🔴 +19.3 kB 🔴 +4.87 kB 🔴 +4.29 kB
assets/SubscriptionPanel-BbMVTF6Y.js (removed) 19.3 kB 🟢 -19.3 kB 🟢 -4.87 kB 🟢 -4.3 kB
assets/AboutPanel-aMPzm36Q.js (removed) 12 kB 🟢 -12 kB 🟢 -3.31 kB 🟢 -2.97 kB
assets/ExtensionPanel-BYeeCnBD.js (removed) 9.63 kB 🟢 -9.63 kB 🟢 -2.74 kB 🟢 -2.44 kB
assets/ExtensionPanel-CFAjUTF5.js (new) 9.63 kB 🔴 +9.63 kB 🔴 +2.74 kB 🔴 +2.43 kB
assets/ServerConfigPanel-B6OSglJB.js (removed) 6.7 kB 🟢 -6.7 kB 🟢 -2.22 kB 🟢 -1.98 kB
assets/ServerConfigPanel-Bvh3YZdd.js (new) 6.7 kB 🔴 +6.7 kB 🔴 +2.22 kB 🔴 +1.98 kB
assets/UserPanel-DYTsOlaN.js (removed) 6.41 kB 🟢 -6.41 kB 🟢 -2.08 kB 🟢 -1.83 kB
assets/UserPanel-X15GRhjS.js (new) 6.41 kB 🔴 +6.41 kB 🔴 +2.08 kB 🔴 +1.82 kB
assets/cloudRemoteConfig-CJH_At_t.js (removed) 1.7 kB 🟢 -1.7 kB 🟢 -832 B 🟢 -703 B
assets/cloudRemoteConfig-DAZ0-0Q_.js (new) 1.7 kB 🔴 +1.7 kB 🔴 +831 B 🔴 +704 B
assets/refreshRemoteConfig-DeQdgz71.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -649 B 🟢 -555 B
assets/refreshRemoteConfig-DZtlzaw_.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +647 B 🔴 +546 B
assets/config-mqWtMfCQ.js 1.79 kB 1.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-9QSSkTCa.js 34.4 kB 34.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B4EA-3pw.js 28.9 kB 28.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B8UriKLj.js 24.6 kB 24.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B9Pwl29v.js 38.8 kB 38.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BA8kOJiA.js 30.7 kB 30.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BmOyNex4.js 32.6 kB 32.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CFFWzLMp.js 24 kB 24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CLz_Hooa.js 28.9 kB 28.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-D0NP2oqy.js 28 kB 28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DeUk0--V.js 30.1 kB 30.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DyMzlRFO.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 10 removed

User & Accounts — 16.8 kB (baseline 16.8 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CePhrjMB.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/auth-DahQzVYm.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/SignUpForm-BltGKXLE.js (new) 3.18 kB 🔴 +3.18 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/SignUpForm-CWifiuDB.js (removed) 3.18 kB 🟢 -3.18 kB 🟢 -1.29 kB 🟢 -1.15 kB
assets/UpdatePasswordContent-Bh2HBt4c.js (removed) 2.52 kB 🟢 -2.52 kB 🟢 -1.13 kB 🟢 -998 B
assets/UpdatePasswordContent-DDeRShsJ.js (new) 2.52 kB 🔴 +2.52 kB 🔴 +1.13 kB 🔴 +992 B
assets/firebaseAuthStore-C5FGNwyG.js (new) 869 B 🔴 +869 B 🔴 +419 B 🔴 +366 B
assets/firebaseAuthStore-vWvW53Ni.js (removed) 869 B 🟢 -869 B 🟢 -418 B 🟢 -371 B
assets/auth-C176E5i8.js (removed) 313 B 🟢 -313 B 🟢 -198 B 🟢 -182 B
assets/auth-CXBM85z7.js (new) 313 B 🔴 +313 B 🔴 +199 B 🔴 +193 B
assets/PasswordFields-C7GvkX0Y.js 4.68 kB 4.68 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-DbdLnJjE.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Editors & Dialogs — 81.9 kB (baseline 81.9 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-BCVl-Dl9.js (new) 81.1 kB 🔴 +81.1 kB 🔴 +16.9 kB 🔴 +14.5 kB
assets/useShareDialog-CxOqSyje.js (removed) 81.1 kB 🟢 -81.1 kB 🟢 -16.9 kB 🟢 -14.5 kB
assets/useSubscriptionDialog-C2KMWlMU.js (new) 817 B 🔴 +817 B 🔴 +410 B 🔴 +352 B
assets/useSubscriptionDialog-g9h7SnKl.js (removed) 817 B 🟢 -817 B 🟢 -410 B 🟢 -353 B

Status: 2 added / 2 removed

UI Components — 59.2 kB (baseline 59.2 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-98iZDvmg.js (removed) 14.3 kB 🟢 -14.3 kB 🟢 -4.01 kB 🟢 -3.57 kB
assets/ComfyQueueButton-Bp9slQyZ.js (new) 14.3 kB 🔴 +14.3 kB 🔴 +4.01 kB 🔴 +3.58 kB
assets/useTerminalTabs-BwM2n_aC.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +3.53 kB 🔴 +3.1 kB
assets/useTerminalTabs-C3Kr2Swg.js (removed) 10.5 kB 🟢 -10.5 kB 🟢 -3.53 kB 🟢 -3.12 kB
assets/SubscribeButton-DhNoQpbg.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.04 kB 🟢 -916 B
assets/SubscribeButton-DRxBkBay.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.04 kB 🔴 +918 B
assets/cloudFeedbackTopbarButton-DmaRDaEg.js (new) 1.51 kB 🔴 +1.51 kB 🔴 +771 B 🔴 +704 B
assets/cloudFeedbackTopbarButton-EYaaQO5O.js (removed) 1.51 kB 🟢 -1.51 kB 🟢 -774 B 🟢 -698 B
assets/ComfyQueueButton-DecV9Fb8.js (new) 874 B 🔴 +874 B 🔴 +425 B 🔴 +372 B
assets/ComfyQueueButton-DLbEoKMv.js (removed) 874 B 🟢 -874 B 🟢 -424 B 🟢 -377 B
assets/Button-CfJrPYd9.js 3.42 kB 3.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-BS8_u69b.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-A8IbMWuw.js 3.94 kB 3.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-CMFGgoZx.js 6.27 kB 6.27 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/toggle-group-BVySyKd5.js 4.03 kB 4.03 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-BZIfuUSE.js 7.53 kB 7.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-B9fMikMO.js 1.24 kB 1.24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-DL1OO9s-.js 2.04 kB 2.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 3.14 MB (baseline 3.14 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-BAiMfjO9.js (removed) 2.14 MB 🟢 -2.14 MB 🟢 -491 kB 🟢 -368 kB
assets/dialogService-CClWcX5z.js (new) 2.14 MB 🔴 +2.14 MB 🔴 +491 kB 🔴 +368 kB
assets/api-4HaKqcQY.js (new) 862 kB 🔴 +862 kB 🔴 +206 kB 🔴 +162 kB
assets/api-Coh8J5BO.js (removed) 862 kB 🟢 -862 kB 🟢 -206 kB 🟢 -162 kB
assets/load3dService-D3Dvyvwi.js (new) 93.4 kB 🔴 +93.4 kB 🔴 +19.8 kB 🔴 +17 kB
assets/load3dService-DxpKJQ-m.js (removed) 93.4 kB 🟢 -93.4 kB 🟢 -19.8 kB 🟢 -17 kB
assets/workflowShareService-CkfbQCHy.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +4.32 kB 🔴 +3.8 kB
assets/workflowShareService-Kw2fAe9K.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -4.32 kB 🟢 -3.8 kB
assets/releaseStore-CMmqexPm.js (removed) 8.11 kB 🟢 -8.11 kB 🟢 -2.27 kB 🟢 -1.99 kB
assets/releaseStore-CTf-CZLy.js (new) 8.11 kB 🔴 +8.11 kB 🔴 +2.27 kB 🔴 +1.99 kB
assets/keybindingService-CgqDvkPq.js (removed) 6.99 kB 🟢 -6.99 kB 🟢 -1.73 kB 🟢 -1.49 kB
assets/keybindingService-DYJ5aMCV.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +1.74 kB 🔴 +1.5 kB
assets/extensionStore-BnFSWDnJ.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.68 kB 🔴 +1.44 kB
assets/extensionStore-h32PhHRJ.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.68 kB 🟢 -1.45 kB
assets/userStore-CSdtgnzq.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -869 B 🟢 -768 B
assets/userStore-DypongQS.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +869 B 🔴 +765 B
assets/bootstrapStore-CEIC31cU.js (removed) 2.11 kB 🟢 -2.11 kB 🟢 -888 B 🟢 -807 B
assets/bootstrapStore-I-OY2f74.js (new) 2.11 kB 🔴 +2.11 kB 🔴 +891 B 🔴 +810 B
assets/audioService-CJmWXCWP.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +864 B 🔴 +748 B
assets/audioService-DJXqXoP6.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -865 B 🟢 -744 B
assets/releaseStore-3JuEnCIH.js (new) 841 B 🔴 +841 B 🔴 +416 B 🔴 +360 B
assets/releaseStore-BtCxkiFt.js (removed) 841 B 🟢 -841 B 🟢 -414 B 🟢 -362 B
assets/workflowDraftStore-C-PKdxpv.js (removed) 817 B 🟢 -817 B 🟢 -409 B 🟢 -359 B
assets/workflowDraftStore-Dnx5DvPd.js (new) 817 B 🔴 +817 B 🔴 +409 B 🔴 +356 B
assets/dialogService-C2T9AgRU.js (removed) 806 B 🟢 -806 B 🟢 -402 B 🟢 -354 B
assets/dialogService-DViouo8n.js (new) 806 B 🔴 +806 B 🔴 +402 B 🔴 +350 B
assets/settingStore-DNsxDwzQ.js (new) 804 B 🔴 +804 B 🔴 +404 B 🔴 +348 B
assets/settingStore-sJ5AIkpt.js (removed) 804 B 🟢 -804 B 🟢 -404 B 🟢 -352 B
assets/assetsStore-BGuOQlpH.js (removed) 803 B 🟢 -803 B 🟢 -404 B 🟢 -351 B
assets/assetsStore-BSoteirs.js (new) 803 B 🔴 +803 B 🔴 +405 B 🔴 +348 B
assets/electronDownloadStore-BNV-_J3D.js 1.83 kB 1.83 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/serverConfigStore-DBKtWI6-.js 2.35 kB 2.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 15 added / 15 removed

Utilities & Hooks — 84.2 kB (baseline 84.2 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3dViewer-C0eTtYsg.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -3.42 kB 🟢 -3.03 kB
assets/useLoad3dViewer-CYtLInFT.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +3.42 kB 🔴 +3.02 kB
assets/useLoad3d-Gl386zt6.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.65 kB 🟢 -3.23 kB
assets/useLoad3d-sXymb-iH.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.65 kB 🔴 +3.23 kB
assets/useFeatureFlags-BsHxvDAs.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.75 kB 🟢 -1.48 kB
assets/useFeatureFlags-DBBeN--J.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.75 kB 🔴 +1.48 kB
assets/useWorkspaceUI-BxqwdXjU.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -979 B 🟢 -811 B
assets/useWorkspaceUI-Dzo8vku8.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +979 B 🔴 +813 B
assets/subscriptionCheckoutUtil-CjcwgOFM.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.32 kB 🔴 +1.15 kB
assets/subscriptionCheckoutUtil-Cq4R5-_N.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.32 kB 🟢 -1.15 kB
assets/useUpstreamValue-D8Uubso7.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -805 B 🟢 -713 B
assets/useUpstreamValue-skVjx0aN.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +805 B 🔴 +714 B
assets/useErrorHandling-BAzrlCR5.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -650 B 🟢 -555 B
assets/useErrorHandling-CBwzU8ko.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +650 B 🔴 +554 B
assets/audioUtils-_djG211a.js (removed) 958 B 🟢 -958 B 🟢 -563 B 🟢 -460 B
assets/audioUtils-zmyLPx96.js (new) 958 B 🔴 +958 B 🔴 +565 B 🔴 +500 B
assets/useLoad3d-Bz8_2J8M.js (removed) 940 B 🟢 -940 B 🟢 -457 B 🟢 -404 B
assets/useLoad3d-uDDmPA3K.js (new) 940 B 🔴 +940 B 🔴 +457 B 🔴 +401 B
assets/useLoad3dViewer-BHRkRETS.js (new) 919 B 🔴 +919 B 🔴 +442 B 🔴 +390 B
assets/useLoad3dViewer-CqzYNYLH.js (removed) 919 B 🟢 -919 B 🟢 -442 B 🟢 -392 B
assets/useCurrentUser-B0MRwVCS.js (new) 803 B 🔴 +803 B 🔴 +406 B 🔴 +349 B
assets/useCurrentUser-C4-GuiHM.js (removed) 803 B 🟢 -803 B 🟢 -405 B 🟢 -353 B
assets/useWorkspaceSwitch-Bb3kf5Vu.js (new) 747 B 🔴 +747 B 🔴 +387 B 🔴 +330 B
assets/useWorkspaceSwitch-D7rjyKqh.js (removed) 747 B 🟢 -747 B 🟢 -387 B 🟢 -334 B
assets/_plugin-vue_export-helper-DqNI4win.js 365 B 365 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/assetMetadataUtils-AhWGxT_I.js 4.78 kB 4.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-DVLSpp7Z.js 8.89 kB 8.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-B_R_XY7v.js 489 B 489 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/formatUtil-f5qiufgt.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Di1r-U2x.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-D-ScwR1F.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-DtIwha9U.js 3.04 kB 3.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 12 added / 12 removed

Vendor & Third-Party — 9.78 MB (baseline 9.78 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-B-zaJ78_.js 93 kB 93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-5Q8F_9hS.js 411 kB 411 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-x5F51RZV.js 1.01 MB 1.01 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-TjUfhse9.js 140 kB 140 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-BAquA4iy.js 110 kB 110 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DllqcebE.js 1.76 MB 1.76 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-Bm5ZkAS2.js 1.75 MB 1.75 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-CCjqphhL.js 474 kB 474 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-Dn2jSJwd.js 267 kB 267 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-7fo2cFbZ.js 1.83 MB 1.83 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-Dk5jn8en.js 737 kB 737 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-Ba0aGEmU.js 328 kB 328 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-DrtiTSko.js 136 kB 136 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-_VieD4Yp.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-B0izGxZ6.js 246 kB 246 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-W_VsqAhz.js 111 kB 111 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 8.22 MB (baseline 8.22 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-C7Hlmc4b.js (removed) 75.9 kB 🟢 -75.9 kB 🟢 -19.6 kB 🟢 -16.7 kB
assets/core-DY-LdiA_.js (new) 75.9 kB 🔴 +75.9 kB 🔴 +19.6 kB 🔴 +16.7 kB
assets/groupNode-BCcbnAKG.js (new) 73.9 kB 🔴 +73.9 kB 🔴 +18.5 kB 🔴 +16.2 kB
assets/groupNode-BpoFAYtZ.js (removed) 73.9 kB 🟢 -73.9 kB 🟢 -18.5 kB 🟢 -16.2 kB
assets/WidgetSelect-Bvk1YWqh.js (removed) 63.2 kB 🟢 -63.2 kB 🟢 -13.8 kB 🟢 -11.9 kB
assets/WidgetSelect-Rk8XPVAc.js (new) 63.2 kB 🔴 +63.2 kB 🔴 +13.8 kB 🔴 +11.9 kB
assets/SubscriptionRequiredDialogContentWorkspace-CASxxEg0.js (new) 47.1 kB 🔴 +47.1 kB 🔴 +8.78 kB 🔴 +7.61 kB
assets/SubscriptionRequiredDialogContentWorkspace-CYWDjKwE.js (removed) 47.1 kB 🟢 -47.1 kB 🟢 -8.78 kB 🟢 -7.6 kB
assets/WidgetPainter-Ct0p3hoq.js (new) 33.2 kB 🔴 +33.2 kB 🔴 +8.04 kB 🔴 +7.13 kB
assets/WidgetPainter-QvvqEKC6.js (removed) 33.2 kB 🟢 -33.2 kB 🟢 -8.04 kB 🟢 -7.14 kB
assets/Load3DControls-Cf2yudgo.js (new) 32.1 kB 🔴 +32.1 kB 🔴 +5.47 kB 🔴 +4.75 kB
assets/Load3DControls-RHCzcLw3.js (removed) 32.1 kB 🟢 -32.1 kB 🟢 -5.47 kB 🟢 -4.75 kB
assets/WorkspacePanelContent-B1KZYRpP.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -6.25 kB 🟢 -5.5 kB
assets/WorkspacePanelContent-Bo-ISl89.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +6.25 kB 🔴 +5.5 kB
assets/SubscriptionRequiredDialogContent-BaA3vQB8.js (new) 26.1 kB 🔴 +26.1 kB 🔴 +6.64 kB 🔴 +5.86 kB
assets/SubscriptionRequiredDialogContent-ool5OXiK.js (removed) 26.1 kB 🟢 -26.1 kB 🟢 -6.64 kB 🟢 -5.86 kB
assets/Load3dViewerContent-BUa-UynD.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +5.31 kB 🔴 +4.63 kB
assets/Load3dViewerContent-C5JEZ8Fz.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -5.31 kB 🟢 -4.63 kB
assets/WidgetImageCrop-Bf5Tdjo6.js (new) 23.2 kB 🔴 +23.2 kB 🔴 +5.78 kB 🔴 +5.1 kB
assets/WidgetImageCrop-CcMTc-fc.js (removed) 23.2 kB 🟢 -23.2 kB 🟢 -5.78 kB 🟢 -5.1 kB
assets/SubscriptionPanelContentWorkspace-B-rbSIU1.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.17 kB 🔴 +4.56 kB
assets/SubscriptionPanelContentWorkspace-BS4MVVAt.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.17 kB 🟢 -4.55 kB
assets/CurrentUserPopoverWorkspace-D0st2tXJ.js (removed) 20.9 kB 🟢 -20.9 kB 🟢 -5.02 kB 🟢 -4.49 kB
assets/CurrentUserPopoverWorkspace-Nc7hgrLg.js (new) 20.9 kB 🔴 +20.9 kB 🔴 +5.02 kB 🔴 +4.49 kB
assets/SignInContent-BoT47wx3.js (removed) 20.1 kB 🟢 -20.1 kB 🟢 -5.19 kB 🟢 -4.54 kB
assets/SignInContent-wx3R45Sz.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.19 kB 🔴 +4.53 kB
assets/WidgetInputNumber-BKP_X0IC.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.29 kB
assets/WidgetInputNumber-HiXZ4UQI.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.83 kB 🟢 -4.3 kB
assets/WidgetRecordAudio-C2SfNg_W.js (removed) 18 kB 🟢 -18 kB 🟢 -5.13 kB 🟢 -4.6 kB
assets/WidgetRecordAudio-DH2COjUv.js (new) 18 kB 🔴 +18 kB 🔴 +5.13 kB 🔴 +4.59 kB
assets/Load3D-DcMHTW85.js (removed) 16.8 kB 🟢 -16.8 kB 🟢 -4.11 kB 🟢 -3.59 kB
assets/Load3D-Dzq0TZs5.js (new) 16.8 kB 🔴 +16.8 kB 🔴 +4.11 kB 🔴 +3.59 kB
assets/load3d-B4ZCRR-e.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.24 kB 🔴 +3.67 kB
assets/load3d-RyN_I34Y.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.24 kB 🟢 -3.67 kB
assets/WidgetCurve-CN4rMgFh.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.92 kB 🔴 +3.54 kB
assets/WidgetCurve-CY65pwoK.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.92 kB 🟢 -3.54 kB
assets/AudioPreviewPlayer-CKh7Y7sw.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.33 kB 🔴 +2.99 kB
assets/AudioPreviewPlayer-CKsQ4jfD.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.33 kB 🟢 -2.98 kB
assets/nodeTemplates-CcXsIxYf.js (removed) 9.41 kB 🟢 -9.41 kB 🟢 -3.31 kB 🟢 -2.91 kB
assets/nodeTemplates-DgC-SM1F.js (new) 9.41 kB 🔴 +9.41 kB 🔴 +3.31 kB 🔴 +2.91 kB
assets/InviteMemberDialogContent-BXMzyHY3.js (new) 7.62 kB 🔴 +7.62 kB 🔴 +2.39 kB 🔴 +2.09 kB
assets/InviteMemberDialogContent-DhzAw2Vh.js (removed) 7.62 kB 🟢 -7.62 kB 🟢 -2.38 kB 🟢 -2.08 kB
assets/Load3DConfiguration-bKT7PY9Y.js (removed) 6.55 kB 🟢 -6.55 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/Load3DConfiguration-QbGLrI2o.js (new) 6.55 kB 🔴 +6.55 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/onboardingCloudRoutes-rC4hJLOC.js (removed) 6.26 kB 🟢 -6.26 kB 🟢 -1.94 kB 🟢 -1.67 kB
assets/onboardingCloudRoutes-SfDq_ELL.js (new) 6.26 kB 🔴 +6.26 kB 🔴 +1.95 kB 🔴 +1.69 kB
assets/WidgetWithControl-BRLduc-f.js (removed) 5.83 kB 🟢 -5.83 kB 🟢 -2.28 kB 🟢 -2.04 kB
assets/WidgetWithControl-T92HkD6C.js (new) 5.83 kB 🔴 +5.83 kB 🔴 +2.28 kB 🔴 +2.05 kB
assets/CreateWorkspaceDialogContent-Bz0FVOAS.js (new) 5.79 kB 🔴 +5.79 kB 🔴 +2.08 kB 🔴 +1.81 kB
assets/CreateWorkspaceDialogContent-C9DivxZO.js (removed) 5.79 kB 🟢 -5.79 kB 🟢 -2.08 kB 🟢 -1.81 kB
assets/FreeTierDialogContent-DKF9oXzT.js (removed) 5.62 kB 🟢 -5.62 kB 🟢 -1.97 kB 🟢 -1.73 kB
assets/FreeTierDialogContent-YGM4o2FS.js (new) 5.62 kB 🔴 +5.62 kB 🔴 +1.97 kB 🔴 +1.74 kB
assets/EditWorkspaceDialogContent-KGZfTuWM.js (removed) 5.59 kB 🟢 -5.59 kB 🟢 -2.04 kB 🟢 -1.78 kB
assets/EditWorkspaceDialogContent-z2pd_wrG.js (new) 5.59 kB 🔴 +5.59 kB 🔴 +2.04 kB 🔴 +1.78 kB
assets/ValueControlPopover-B97C3-IH.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.85 kB 🔴 +1.65 kB
assets/ValueControlPopover-ByIabBZV.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.85 kB 🟢 -1.65 kB
assets/Preview3d-DhWpl5lb.js (removed) 5.16 kB 🟢 -5.16 kB 🟢 -1.7 kB 🟢 -1.48 kB
assets/Preview3d-DYPcUWDx.js (new) 5.16 kB 🔴 +5.16 kB 🔴 +1.7 kB 🔴 +1.48 kB
assets/WidgetTextarea-B4eanM0K.js (removed) 5.11 kB 🟢 -5.11 kB 🟢 -2 kB 🟢 -1.79 kB
assets/WidgetTextarea-CR_5yNnw.js (new) 5.11 kB 🔴 +5.11 kB 🔴 +2 kB 🔴 +1.77 kB
assets/CancelSubscriptionDialogContent-C88SzgPh.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/CancelSubscriptionDialogContent-D5haGtoo.js (removed) 5.07 kB 🟢 -5.07 kB 🟢 -1.87 kB 🟢 -1.64 kB
assets/DeleteWorkspaceDialogContent-CK1QltFq.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/DeleteWorkspaceDialogContent-DPNJb7Ql.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.72 kB 🔴 +1.5 kB
assets/tierBenefits-CpKt5F8o.js (new) 4.47 kB 🔴 +4.47 kB 🔴 +1.58 kB 🔴 +1.36 kB
assets/tierBenefits-MfHEas8x.js (removed) 4.47 kB 🟢 -4.47 kB 🟢 -1.58 kB 🟢 -1.36 kB
assets/LeaveWorkspaceDialogContent-CJIJ_e4v.js (removed) 4.32 kB 🟢 -4.32 kB 🟢 -1.67 kB 🟢 -1.45 kB
assets/LeaveWorkspaceDialogContent-DZ317A3A.js (new) 4.32 kB 🔴 +4.32 kB 🔴 +1.67 kB 🔴 +1.45 kB
assets/RemoveMemberDialogContent-CUBn8hxt.js (removed) 4.3 kB 🟢 -4.3 kB 🟢 -1.62 kB 🟢 -1.42 kB
assets/RemoveMemberDialogContent-o9RGCMl7.js (new) 4.3 kB 🔴 +4.3 kB 🔴 +1.62 kB 🔴 +1.42 kB
assets/RevokeInviteDialogContent-BO06EyyC.js (removed) 4.21 kB 🟢 -4.21 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/RevokeInviteDialogContent-QTHDq5Tn.js (new) 4.21 kB 🔴 +4.21 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/InviteMemberUpsellDialogContent-csvuLmr6.js (new) 4.11 kB 🔴 +4.11 kB 🔴 +1.49 kB 🔴 +1.3 kB
assets/InviteMemberUpsellDialogContent-dtuKSI8r.js (removed) 4.11 kB 🟢 -4.11 kB 🟢 -1.49 kB 🟢 -1.31 kB
assets/cloudSessionCookie-CjvhMiX4.js (new) 3.98 kB 🔴 +3.98 kB 🔴 +1.42 kB 🔴 +1.22 kB
assets/cloudSessionCookie-CZQ0FXBu.js (removed) 3.98 kB 🟢 -3.98 kB 🟢 -1.42 kB 🟢 -1.23 kB
assets/saveMesh-C213ucxO.js (new) 3.5 kB 🔴 +3.5 kB 🔴 +1.5 kB 🔴 +1.32 kB
assets/saveMesh-Do4wLW5M.js (removed) 3.5 kB 🟢 -3.5 kB 🟢 -1.51 kB 🟢 -1.33 kB
assets/GlobalToast-CrPF6UIn.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/GlobalToast-CUeAxlWF.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/SubscribeToRun-CW7OWITG.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -977 B 🟢 -850 B
assets/SubscribeToRun-DRm751Bb.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +979 B 🔴 +866 B
assets/CloudRunButtonWrapper-CpaiCZSu.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -844 B 🟢 -774 B
assets/CloudRunButtonWrapper-rxlR2QT6.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +844 B 🔴 +771 B
assets/cloudBadges-BQzEDCd5.js (removed) 1.61 kB 🟢 -1.61 kB 🟢 -820 B 🟢 -725 B
assets/cloudBadges-CI3qkK_o.js (new) 1.61 kB 🔴 +1.61 kB 🔴 +820 B 🔴 +739 B
assets/previousFullPath-BYijf47W.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -694 B 🟢 -619 B
assets/previousFullPath-DEVlCnuF.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +693 B 🔴 +601 B
assets/cloudSubscription-6EMlw8Yv.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -744 B 🟢 -636 B
assets/cloudSubscription-D-bubiui.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +739 B 🔴 +628 B
assets/Load3D-DoK0tFok.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +528 B 🔴 +461 B
assets/Load3D-MpLc4R8s.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -530 B 🟢 -468 B
assets/nightlyBadges-Be5Q--Y-.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -587 B 🟢 -517 B
assets/nightlyBadges-Co4x45WQ.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +587 B 🔴 +520 B
assets/Load3dViewerContent-BJ8ouonz.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -499 B 🟢 -437 B
assets/Load3dViewerContent-Cp6EFby8.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +499 B 🔴 +437 B
assets/SubscriptionPanelContentWorkspace-B-GD7wBg.js (new) 1 kB 🔴 +1 kB 🔴 +469 B 🔴 +398 B
assets/SubscriptionPanelContentWorkspace-CVTkP7k3.js (removed) 1 kB 🟢 -1 kB 🟢 -468 B 🟢 -404 B
assets/WidgetLegacy-D9_RqjnQ.js (removed) 825 B 🟢 -825 B 🟢 -416 B 🟢 -357 B
assets/WidgetLegacy-DPB1fU8T.js (new) 825 B 🔴 +825 B 🔴 +416 B 🔴 +354 B
assets/graphHasMissingNodes-BrChqBI0.js (new) 822 B 🔴 +822 B 🔴 +414 B 🔴 +347 B
assets/graphHasMissingNodes-D9qmGXHy.js (removed) 822 B 🟢 -822 B 🟢 -416 B 🟢 -347 B
assets/changeTracker-b60k4pXW.js (new) 801 B 🔴 +801 B 🔴 +405 B 🔴 +350 B
assets/changeTracker-DH-CBGnu.js (removed) 801 B 🟢 -801 B 🟢 -405 B 🟢 -352 B
assets/AnimationControls-bezowaZd.js 4.78 kB 4.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-CG4Vvk-Q.js 2.86 kB 2.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-DVZM9Hkc.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-CoDEVLFQ.js 1.92 kB 1.92 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-Dyos5lPW.js 272 B 272 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-DjEVw9y6.js 960 B 960 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B1YxbAAl.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BgfvFMt0.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bh9JYwnU.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BRWVNs15.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CD22-YFx.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CKIuqKev.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CSGg3k5v.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D4UhEqHD.js 17.2 kB 17.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DEyEeqxL.js 15.3 kB 15.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DKW4HmKH.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DXhyXTX3.js 19.1 kB 19.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-Bh1Ukzcs.js 766 B 766 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-C77mnxOk.js 553 kB 553 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-nnukaC2U.js 137 B 137 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Loader-BAvtaqwA.js 1.26 kB 1.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B-xPA_ME.js 204 kB 204 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-bBw4O-bA.js 188 kB 188 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bgq3FOSq.js 163 kB 163 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bjk1n_HZ.js 165 kB 165 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CPDYMHSJ.js 228 kB 228 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CSyeqaEK.js 168 kB 168 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DYD0RsRt.js 171 kB 171 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-eQ8lAXxU.js 162 kB 162 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-O9L-isT1.js 144 kB 144 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-OtIQojWi.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-RqAOPy6z.js 196 kB 196 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-yc5aD_uG.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DIuvS82I.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-B8bUltoc.js 2.02 kB 2.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-E47jEBXy.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-BkPIHAIK.js 1.06 kB 1.06 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-eDWtQoSO.js 2.94 kB 2.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/missingModelDownload-CDZlvrCp.js 4.05 kB 4.05 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/missingModelDownload-DPbku2pv.js 267 B 267 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/NightlySurveyController-zH2sn5x9.js 9.5 kB 9.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C_IlCaTY.js 369 kB 369 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C55R_xnH.js 411 kB 411 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CaYvcS-e.js 500 kB 500 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CEMVS_MF.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-COCkXQbs.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CWB-5pw4.js 406 kB 406 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D_PHcjIu.js 423 kB 423 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D84pIz-A.js 458 kB 458 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-gkfihp9l.js 373 kB 373 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-IO5Km4WB.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-TSWF2p3s.js 459 kB 459 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-BXCaujAi.js 3.77 kB 3.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DBfy44LZ.js 2.02 kB 2.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-CSKcLDnn.js 9.8 kB 9.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-C9iUnCRt.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-D198AJYP.js 3.57 kB 3.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-CAUfc4gs.js 290 B 290 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-DKpgoSPJ.js 2.28 kB 2.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-7ZMuZPoG.js 443 B 443 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Textarea-CfLZu4GT.js 1.42 kB 1.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-BqIM6TDt.js 313 B 313 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-FL3qajR5.js 1.51 kB 1.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DloL8--t.js 3.5 kB 3.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-C2TkJ416.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-GgFcQfOn.js 3.62 kB 3.62 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-DD71Vf1E.js 2.41 kB 2.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-BdcBT55F.js 16.2 kB 16.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-BP74JvEw.js 3.8 kB 3.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-Du5HZfqN.js 7.78 kB 7.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-BKkdW4B1.js 3.09 kB 3.09 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-BfWyH3b-.js 2.37 kB 2.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-DXPuE1xE.js 3.13 kB 3.13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-XFbYXzAf.js 1.52 kB 1.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-Bwh6qUVP.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-ju3asrao.js 416 B 416 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 52 added / 52 removed

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

⚡ Performance Report

⚠️ 3 regressions detected

Metric Baseline PR (n=3) Δ Sig
canvas-mouse-sweep: event listeners 5 13 +138% ⚠️ z=11.0
large-graph-pan: DOM nodes 20 22 +10% ⚠️ z=3.5
workflow-execution: frame duration 17ms 17ms +0% ⚠️ z=2.0
All metrics
Metric Baseline PR (n=3) Δ Sig
canvas-idle: style recalcs 11 11 -3% z=-0.6
canvas-idle: layouts 0 0 +0%
canvas-idle: task duration 389ms 333ms -14% z=-3.8
canvas-idle: DOM nodes 22 21 -3% z=-1.0
canvas-idle: script duration 24ms 20ms -18% z=-2.2
canvas-idle: event listeners 8 6 -25% z=-1.3
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: frame duration 17ms 17ms -0% z=-1.6
canvas-mouse-sweep: style recalcs 78 76 -2% z=-0.7
canvas-mouse-sweep: layouts 12 12 +0%
canvas-mouse-sweep: task duration 900ms 747ms -17% z=-1.5
canvas-mouse-sweep: DOM nodes 61 61 +1% z=0.1
canvas-mouse-sweep: script duration 133ms 131ms -2% z=-1.1
canvas-mouse-sweep: event listeners 5 13 +138% ⚠️ z=11.0
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: frame duration 17ms 17ms -0% z=-0.7
canvas-zoom-sweep: style recalcs 31 30 -2% z=-1.3
canvas-zoom-sweep: layouts 6 6 +0%
canvas-zoom-sweep: task duration 311ms 282ms -9% z=-2.7
canvas-zoom-sweep: DOM nodes 80 78 -2% z=-1.9
canvas-zoom-sweep: script duration 26ms 23ms -12% z=-1.7
canvas-zoom-sweep: event listeners 20 20 +3% z=-0.7
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: frame duration 17ms 17ms -0% z=1.2
dom-widget-clipping: style recalcs 14 13 -5% z=-0.4
dom-widget-clipping: layouts 0 0 +0%
dom-widget-clipping: task duration 356ms 338ms -5% z=-7.3
dom-widget-clipping: DOM nodes 24 22 -10% z=-0.5
dom-widget-clipping: script duration 68ms 65ms -5% z=-1.8
dom-widget-clipping: event listeners 18 10 -44% variance too high
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: frame duration 17ms 17ms -0% z=-1.4
large-graph-idle: style recalcs 12 11 -6% z=-1.1
large-graph-idle: layouts 0 0 +0%
large-graph-idle: task duration 518ms 469ms -9% z=-2.3
large-graph-idle: DOM nodes 26 23 -13% z=-3.5
large-graph-idle: script duration 102ms 90ms -12% z=-3.0
large-graph-idle: event listeners 30 14 -53% z=-3.7
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: frame duration 17ms 17ms +0% z=-0.4
large-graph-pan: style recalcs 70 70 +0% z=1.9
large-graph-pan: layouts 0 0 +0%
large-graph-pan: task duration 1085ms 1012ms -7% z=-2.4
large-graph-pan: DOM nodes 20 22 +10% ⚠️ z=3.5
large-graph-pan: script duration 408ms 406ms -1% z=-0.5
large-graph-pan: event listeners 4 6 +50% z=1.7
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: frame duration 17ms 17ms +0% z=1.2
minimap-idle: style recalcs 10 10 +7% z=1.6
minimap-idle: layouts 0 0 +0%
minimap-idle: task duration 494ms 455ms -8% z=-1.8
minimap-idle: DOM nodes 19 21 +7% z=1.6
minimap-idle: script duration 95ms 85ms -11% z=-2.3
minimap-idle: event listeners 4 6 +50% z=1.8
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: frame duration 17ms 17ms +0% z=1.4
subgraph-dom-widget-clipping: style recalcs 48 48 +0% z=1.4
subgraph-dom-widget-clipping: layouts 0 0 +0%
subgraph-dom-widget-clipping: task duration 368ms 355ms -4% z=-1.7
subgraph-dom-widget-clipping: DOM nodes 23 23 +1% z=0.6
subgraph-dom-widget-clipping: script duration 125ms 125ms -0% z=-1.2
subgraph-dom-widget-clipping: event listeners 16 16 +0% z=0.0
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: frame duration 17ms 17ms -0% z=0.2
subgraph-idle: style recalcs 11 12 +3% z=1.4
subgraph-idle: layouts 0 0 +0%
subgraph-idle: task duration 355ms 327ms -8% z=-1.7
subgraph-idle: DOM nodes 23 23 +0% z=0.5
subgraph-idle: script duration 19ms 17ms -12% z=-1.6
subgraph-idle: event listeners 13 14 +5% z=0.8
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: frame duration 17ms 17ms -0% z=-2.0
subgraph-mouse-sweep: style recalcs 77 80 +5% z=0.1
subgraph-mouse-sweep: layouts 16 16 +0%
subgraph-mouse-sweep: task duration 706ms 712ms +1% z=-0.6
subgraph-mouse-sweep: DOM nodes 63 66 +5% z=-0.1
subgraph-mouse-sweep: script duration 103ms 92ms -11% z=-2.9
subgraph-mouse-sweep: event listeners 4 5 +17% variance too high
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: frame duration 17ms 17ms +0% z=0.2
workflow-execution: style recalcs 20 18 -7% z=-0.0
workflow-execution: layouts 5 5 -6% z=-0.2
workflow-execution: task duration 130ms 117ms -10% z=-4.1
workflow-execution: DOM nodes 160 160 +0% z=0.7
workflow-execution: script duration 30ms 28ms -8% z=-1.7
workflow-execution: event listeners 55 55 +0% z=0.4
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: frame duration 17ms 17ms +0% ⚠️ z=2.0
Historical variance (last 5 runs)
Metric μ σ CV
canvas-idle: style recalcs 11 1 6.5%
canvas-idle: layouts 0 0 0.0%
canvas-idle: task duration 384ms 14ms 3.5%
canvas-idle: DOM nodes 22 1 5.3%
canvas-idle: script duration 23ms 2ms 6.7%
canvas-idle: event listeners 11 4 34.2%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: frame duration 17ms 0ms 0.0%
canvas-mouse-sweep: style recalcs 77 2 2.2%
canvas-mouse-sweep: layouts 12 0 0.0%
canvas-mouse-sweep: task duration 838ms 61ms 7.3%
canvas-mouse-sweep: DOM nodes 61 2 3.2%
canvas-mouse-sweep: script duration 137ms 5ms 4.0%
canvas-mouse-sweep: event listeners 5 1 12.5%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: frame duration 17ms 0ms 0.0%
canvas-zoom-sweep: style recalcs 31 1 2.1%
canvas-zoom-sweep: layouts 6 0 0.0%
canvas-zoom-sweep: task duration 324ms 16ms 4.8%
canvas-zoom-sweep: DOM nodes 80 1 1.5%
canvas-zoom-sweep: script duration 28ms 3ms 9.7%
canvas-zoom-sweep: event listeners 27 10 37.3%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: frame duration 17ms 0ms 0.0%
dom-widget-clipping: style recalcs 13 0 2.9%
dom-widget-clipping: layouts 0 0 0.0%
dom-widget-clipping: task duration 360ms 3ms 0.8%
dom-widget-clipping: DOM nodes 22 1 5.6%
dom-widget-clipping: script duration 69ms 3ms 3.6%
dom-widget-clipping: event listeners 8 7 79.7%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: frame duration 17ms 0ms 0.0%
large-graph-idle: style recalcs 12 0 3.1%
large-graph-idle: layouts 0 0 0.0%
large-graph-idle: task duration 533ms 27ms 5.1%
large-graph-idle: DOM nodes 25 1 2.6%
large-graph-idle: script duration 105ms 5ms 4.8%
large-graph-idle: event listeners 28 4 13.4%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: frame duration 17ms 0ms 0.0%
large-graph-pan: style recalcs 69 1 0.8%
large-graph-pan: layouts 0 0 0.0%
large-graph-pan: task duration 1086ms 30ms 2.8%
large-graph-pan: DOM nodes 19 1 5.3%
large-graph-pan: script duration 415ms 17ms 4.1%
large-graph-pan: event listeners 5 1 19.2%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: frame duration 17ms 0ms 0.0%
minimap-idle: style recalcs 10 0 4.2%
minimap-idle: layouts 0 0 0.0%
minimap-idle: task duration 500ms 25ms 5.0%
minimap-idle: DOM nodes 19 1 4.2%
minimap-idle: script duration 97ms 5ms 5.4%
minimap-idle: event listeners 4 1 20.3%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: frame duration 17ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalcs 48 0 0.6%
subgraph-dom-widget-clipping: layouts 0 0 0.0%
subgraph-dom-widget-clipping: task duration 373ms 11ms 2.9%
subgraph-dom-widget-clipping: DOM nodes 22 1 3.9%
subgraph-dom-widget-clipping: script duration 128ms 3ms 2.3%
subgraph-dom-widget-clipping: event listeners 16 6 35.4%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: frame duration 17ms 0ms 0.0%
subgraph-idle: style recalcs 11 0 2.1%
subgraph-idle: layouts 0 0 0.0%
subgraph-idle: task duration 364ms 22ms 6.1%
subgraph-idle: DOM nodes 23 1 2.8%
subgraph-idle: script duration 20ms 2ms 9.8%
subgraph-idle: event listeners 10 5 43.9%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: frame duration 17ms 0ms 0.0%
subgraph-mouse-sweep: style recalcs 80 4 4.8%
subgraph-mouse-sweep: layouts 16 0 0.0%
subgraph-mouse-sweep: task duration 772ms 101ms 13.1%
subgraph-mouse-sweep: DOM nodes 67 4 6.6%
subgraph-mouse-sweep: script duration 103ms 4ms 3.6%
subgraph-mouse-sweep: event listeners 8 4 52.8%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: frame duration 17ms 0ms 0.0%
workflow-execution: style recalcs 18 1 6.9%
workflow-execution: layouts 5 0 5.5%
workflow-execution: task duration 130ms 3ms 2.4%
workflow-execution: DOM nodes 159 1 0.8%
workflow-execution: script duration 32ms 3ms 8.8%
workflow-execution: event listeners 54 2 3.9%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: frame duration 17ms 0ms 0.0%
Raw data
{
  "timestamp": "2026-03-16T01:18:29.422Z",
  "gitSha": "a06875e67dd734868a1849ffb8d3465d55ffaa2b",
  "branch": "bl/coderabbit-block-e2e-check",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2007.3859999999968,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 334.187,
      "heapDeltaBytes": 1833184,
      "domNodes": 22,
      "jsHeapTotalBytes": 17563648,
      "scriptDurationMs": 19.403999999999996,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "canvas-idle",
      "durationMs": 2027.629999999931,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.189,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 324.851,
      "heapDeltaBytes": 1678796,
      "domNodes": 20,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 20.221,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "canvas-idle",
      "durationMs": 2019.233999999983,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.449,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 338.64700000000005,
      "heapDeltaBytes": 1060832,
      "domNodes": 22,
      "jsHeapTotalBytes": 17563648,
      "scriptDurationMs": 19.673000000000002,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1917.4170000000004,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 47.495000000000005,
      "layouts": 12,
      "layoutDurationMs": 3.399,
      "taskDurationMs": 811.164,
      "heapDeltaBytes": -2408600,
      "domNodes": 67,
      "jsHeapTotalBytes": 18350080,
      "scriptDurationMs": 137.60399999999998,
      "eventListeners": 30,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1791.0669999999982,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 35.364,
      "layouts": 12,
      "layoutDurationMs": 3.246,
      "taskDurationMs": 703.145,
      "heapDeltaBytes": -2927016,
      "domNodes": 60,
      "jsHeapTotalBytes": 17825792,
      "scriptDurationMs": 123.978,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1784.9340000000211,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 38.204,
      "layouts": 12,
      "layoutDurationMs": 3.585,
      "taskDurationMs": 726.5520000000001,
      "heapDeltaBytes": -2772072,
      "domNodes": 56,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 130.503,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1762.061000000017,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 15.243000000000002,
      "layouts": 6,
      "layoutDurationMs": 0.591,
      "taskDurationMs": 284.00199999999995,
      "heapDeltaBytes": 5753144,
      "domNodes": 78,
      "jsHeapTotalBytes": 17825792,
      "scriptDurationMs": 24.12600000000001,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1726.2789999999768,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 16.264,
      "layouts": 6,
      "layoutDurationMs": 0.5710000000000001,
      "taskDurationMs": 282.69,
      "heapDeltaBytes": 5510172,
      "domNodes": 79,
      "jsHeapTotalBytes": 18350080,
      "scriptDurationMs": 22.265,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1748.7800000000107,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 15.131999999999998,
      "layouts": 6,
      "layoutDurationMs": 0.515,
      "taskDurationMs": 279.33799999999997,
      "heapDeltaBytes": 5781936,
      "domNodes": 77,
      "jsHeapTotalBytes": 17301504,
      "scriptDurationMs": 22.367,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 555.3150000000073,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.339999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 333.3039999999999,
      "heapDeltaBytes": 13403116,
      "domNodes": 18,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 64.696,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 562.9740000000538,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 9.111,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 348.39200000000005,
      "heapDeltaBytes": 13371268,
      "domNodes": 23,
      "jsHeapTotalBytes": 14417920,
      "scriptDurationMs": 66.204,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 546.493000000055,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 9.906,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 332.199,
      "heapDeltaBytes": 13412644,
      "domNodes": 24,
      "jsHeapTotalBytes": 14417920,
      "scriptDurationMs": 63.126000000000005,
      "eventListeners": 26,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2021.400999999969,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.918,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 469.687,
      "heapDeltaBytes": -10548956,
      "domNodes": 22,
      "jsHeapTotalBytes": 8769536,
      "scriptDurationMs": 89.027,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-idle",
      "durationMs": 1996.0199999999304,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 11.997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 467.896,
      "heapDeltaBytes": -10661024,
      "domNodes": 25,
      "jsHeapTotalBytes": 7720960,
      "scriptDurationMs": 89.70199999999998,
      "eventListeners": 30,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "large-graph-idle",
      "durationMs": 1989.7190000000364,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.009,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 470.18,
      "heapDeltaBytes": -10812828,
      "domNodes": 22,
      "jsHeapTotalBytes": 7983104,
      "scriptDurationMs": 91.372,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2108.525000000043,
      "styleRecalcs": 71,
      "styleRecalcDurationMs": 17.424,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1036.865,
      "heapDeltaBytes": 5271964,
      "domNodes": 24,
      "jsHeapTotalBytes": 12115968,
      "scriptDurationMs": 430.694,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.680000000000017
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2134.4929999999067,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 16.052999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1028.951,
      "heapDeltaBytes": 847792,
      "domNodes": 20,
      "jsHeapTotalBytes": 11067392,
      "scriptDurationMs": 406.01,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2068.104000000062,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 16.226,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 971.23,
      "heapDeltaBytes": 3917160,
      "domNodes": 22,
      "jsHeapTotalBytes": 10280960,
      "scriptDurationMs": 380.84299999999996,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "minimap-idle",
      "durationMs": 2003.8019999999506,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.500999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 455.214,
      "heapDeltaBytes": -11247952,
      "domNodes": 22,
      "jsHeapTotalBytes": 8507392,
      "scriptDurationMs": 85.99000000000001,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "minimap-idle",
      "durationMs": 2019.894000000022,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.958,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 444.53499999999997,
      "heapDeltaBytes": -11999480,
      "domNodes": 18,
      "jsHeapTotalBytes": 9318400,
      "scriptDurationMs": 81.41199999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "minimap-idle",
      "durationMs": 2026.1750000000802,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.833,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 464.243,
      "heapDeltaBytes": -10471448,
      "domNodes": 22,
      "jsHeapTotalBytes": 6934528,
      "scriptDurationMs": 87.35699999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 587.6230000000078,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 12.607999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 365.692,
      "heapDeltaBytes": 13172136,
      "domNodes": 25,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 128.706,
      "eventListeners": 32,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 533.7490000000571,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 11.290000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 347.835,
      "heapDeltaBytes": 12917072,
      "domNodes": 22,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 122.404,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000027
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 560.1369999999406,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 11.526,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 351.221,
      "heapDeltaBytes": 13175708,
      "domNodes": 22,
      "jsHeapTotalBytes": 13369344,
      "scriptDurationMs": 123.276,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1994.784999999979,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 9.941000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 327.24199999999996,
      "heapDeltaBytes": 837688,
      "domNodes": 23,
      "jsHeapTotalBytes": 17301504,
      "scriptDurationMs": 15.675999999999995,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2005.2789999999732,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.469000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 323.90799999999996,
      "heapDeltaBytes": 857080,
      "domNodes": 21,
      "jsHeapTotalBytes": 17301504,
      "scriptDurationMs": 16.697999999999997,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2007.4529999999413,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 11.091999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 329.72799999999995,
      "heapDeltaBytes": 24203800,
      "domNodes": 25,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 17.763000000000005,
      "eventListeners": 30,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1992.6759999999604,
      "styleRecalcs": 88,
      "styleRecalcDurationMs": 47.992000000000004,
      "layouts": 16,
      "layoutDurationMs": 4.174,
      "taskDurationMs": 887.002,
      "heapDeltaBytes": -7152716,
      "domNodes": 73,
      "jsHeapTotalBytes": 16777216,
      "scriptDurationMs": 95.913,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.680000000000017
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1675.9940000000597,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 34.162,
      "layouts": 16,
      "layoutDurationMs": 4.25,
      "taskDurationMs": 624.5690000000001,
      "heapDeltaBytes": -6841396,
      "domNodes": 62,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 89.77,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1709.023000000002,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 37.571,
      "layouts": 16,
      "layoutDurationMs": 4.5520000000000005,
      "taskDurationMs": 623.907,
      "heapDeltaBytes": -7107264,
      "domNodes": 64,
      "jsHeapTotalBytes": 17039360,
      "scriptDurationMs": 91.318,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "workflow-execution",
      "durationMs": 434.61899999999787,
      "styleRecalcs": 18,
      "styleRecalcDurationMs": 25.208000000000002,
      "layouts": 5,
      "layoutDurationMs": 1.398,
      "taskDurationMs": 119.26,
      "heapDeltaBytes": 4540416,
      "domNodes": 166,
      "jsHeapTotalBytes": 4456448,
      "scriptDurationMs": 26.786000000000005,
      "eventListeners": 55,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "workflow-execution",
      "durationMs": 429.37499999993634,
      "styleRecalcs": 18,
      "styleRecalcDurationMs": 23.593999999999998,
      "layouts": 5,
      "layoutDurationMs": 1.4169999999999998,
      "taskDurationMs": 112.06500000000001,
      "heapDeltaBytes": 4291488,
      "domNodes": 156,
      "jsHeapTotalBytes": 4456448,
      "scriptDurationMs": 25.089,
      "eventListeners": 55,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "workflow-execution",
      "durationMs": 437.5550000000885,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 23.45,
      "layouts": 5,
      "layoutDurationMs": 1.394,
      "taskDurationMs": 119.79599999999999,
      "heapDeltaBytes": 4407728,
      "domNodes": 158,
      "jsHeapTotalBytes": 4456448,
      "scriptDurationMs": 30.886999999999997,
      "eventListeners": 55,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000027
    }
  ]
}

@christian-byrne christian-byrne merged commit e31d98b into main Mar 16, 2026
34 checks passed
@christian-byrne christian-byrne deleted the bl/coderabbit-block-e2e-check branch March 16, 2026 02:06
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
christian-byrne pushed a commit that referenced this pull request Mar 17, 2026
## 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.
christian-byrne added a commit that referenced this pull request Mar 17, 2026
## 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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

## 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)
christian-byrne added a commit that referenced this pull request Mar 17, 2026
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.

![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants