Skip to content

feat: surface missing models in Error Tab for OSS and remove legacy dialog#9921

Merged
jaeone94 merged 21 commits intomainfrom
feat/oss-missing-model-error-tab
Mar 15, 2026
Merged

feat: surface missing models in Error Tab for OSS and remove legacy dialog#9921
jaeone94 merged 21 commits intomainfrom
feat/oss-missing-model-error-tab

Conversation

@jaeone94
Copy link
Collaborator

@jaeone94 jaeone94 commented Mar 14, 2026

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

  • Unit tests pass (86 tests)
  • TypeScript typecheck passes
  • knip passes
  • Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory
  • Download button triggers browser download with correct URL
  • Download All button downloads all downloadable models
  • Cloud environment behavior unchanged
  • Playwright E2E: pnpm test:browser:local -- --grep "Missing models in Error Tab"

Screenshots

2026-03-15.012443.mp4

┆Issue is synchronized with this Notion page by Unito

- Remove isCloud guard from scanAllModelCandidates and surfaceMissingModels
  so OSS users see missing models in the Error Tab
- Add Download button for OSS models with file size display
- Group non-asset-supported models by directory instead of UNSUPPORTED in OSS
- Move download business logic from dialog/content to platform/missingModel
- Add folderPaths/fileSizes to missingModelStore with setter methods
- Add race condition guard for fileSizes fetch
- Add OSS grouping and display tests
- Move download business logic to platform/missingModel/missingModelDownload
- Fix desktop download by awaiting folderPaths before surfacing models
- Remove fragile reference-equality guard for fileSizes fetch
- Add store setter methods for folderPaths and fileSizes
- Add aria-label with model name to Download button
- Add aria-expanded to expand/collapse toggle
- Add warning icon for unknown category header (WCAG 1.4.1)
- Remove collapseState change-detector test
- Add Cloud environment contrast tests for missingModelGroups
- Add cross-reference comments for extension constant lists
- Remove legacy dialog components and composable
  (MissingModelsContent, MissingModelsHeader, MissingModelsFooter,
   useMissingModelsDialog, missingModelsUtils)
- Remove dialog invocation from workflowService
- Add Download All button to missing model group header in Error Tab
  with total file size display
- Add downloadAll i18n key under rightSidePanel.missingModels
- Rewrite Playwright tests to verify error overlay and Error Tab
  instead of legacy dialog title/buttons
- Enable UseNewMenu and ShowErrorsTab settings in test setup
- Remove API-mock-based model installation test (incompatible with
  OSS combo-options detection path)
- Remove ShowMissingModelsWarning references from fixtures and templates
- Remove redundant text-warning-background class on inner span
- Add error overlay visibility assertion for widget mismatch test
- Fix hasValidDirectory to check for non-empty array
- Improve test isolation with explicit mockIsCloud reset
- Extract makeModel helper to module level to reduce duplication
- Add cross-reference comments for extension constants
@jaeone94 jaeone94 requested a review from a team as a code owner March 14, 2026 16:21
@jaeone94 jaeone94 requested a review from a team as a code owner March 14, 2026 16:21
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 14, 2026
@github-actions
Copy link

github-actions bot commented Mar 14, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/15/2026, 01:16:49 PM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

The PR removes the dialog-based missing-models UI and composable, migrates missing-model reporting into the right-side error panel, adds per-URL size tracking and a "download all" flow, unifies cloud/non-cloud candidate scanning and metadata fetch, and updates tests and store state for folder paths and file sizes.

Changes

Cohort / File(s) Summary
Dialog UI & composable removal
src/components/dialog/content/MissingModelsContent.vue, src/components/dialog/content/MissingModelsFooter.vue, src/components/dialog/content/MissingModelsHeader.vue, src/composables/useMissingModelsDialog.ts, src/components/dialog/content/missingModelsUtils.test.ts
Removed dialog SFCs, header/footer, the useMissingModelsDialog composable, and associated unit tests.
Right-side error panel integration
src/components/rightSidePanel/errors/TabErrors.vue, src/components/rightSidePanel/errors/useErrorGroups.ts, src/components/rightSidePanel/errors/useErrorGroups.test.ts
Added download-all UI/logic and size label in errors panel; changed grouping to include directory buckets for non-cloud; expanded OSS vs Cloud tests and helpers.
Platform missing-model components & tests
src/platform/missingModel/components/MissingModelCard.vue, src/platform/missingModel/components/MissingModelCard.test.ts, src/platform/missingModel/components/MissingModelRow.vue, src/platform/missingModel/components/MissingModelUrlInput.vue
Gated warning styling and notices by cloud; added per-row download button, size label, and handler; spacing tweak in URL input; added OSS/Cloud test scenarios.
Download & scan utilities
src/platform/missingModel/missingModelDownload.ts, src/platform/missingModel/missingModelScan.ts
Made ModelWithUrl non-exported, removed some exported badge/helpers, tightened metadata/file URL checks and Content-Length parsing, and added exported MODEL_FILE_EXTENSIONS.
Missing-model store
src/platform/missingModel/missingModelStore.ts
Added reactive folderPaths and fileSizes state plus setFolderPaths and setFileSize methods; reset on clear.
Workflow & app pipeline
src/platform/workflow/core/services/workflowService.ts, src/platform/workflow/core/services/workflowService.test.ts, src/scripts/app.ts
Removed missing-model dialog invocation from workflow service; unified candidate scanning for cloud/non-cloud, added folder-path fetch and per-model metadata enrichment for non-cloud, and surface missing models via store/panel.
Browser tests & fixtures
browser_tests/fixtures/ComfyPage.ts, browser_tests/tests/dialog.spec.ts, browser_tests/tests/templates.spec.ts
Removed/changed ShowMissingModelsWarning setup; replaced dialog assertions with error-overlay and "Missing Models" checks; aligned tests to error-panel flow.
Localization
src/locales/en/main.json
Added "downloadAll" locale key used by the new download UI.

Sequence Diagram

sequenceDiagram
    participant App as App Init
    participant Scanner as Candidate Scanner
    participant Store as MissingModelStore
    participant Cloud as Cloud Detector
    participant Verifier as Asset Verifier
    participant Panel as Right-side Error Panel
    participant Downloader as Download Handler

    rect rgba(180,100,100,0.5)
    Note over App,Panel: Legacy dialog flow removed
    App->>Panel: (old) show missing-models dialog
    end

    rect rgba(100,180,120,0.5)
    Note over App,Scanner: New unified scan & surface flow
    App->>Scanner: scanAllModelCandidates(getDirectory)
    Scanner->>Store: add/update candidate entries
    App->>Cloud: isCloud?
    alt Cloud
        App->>Verifier: verify asset availability (asset-based filter)
        Verifier-->>Store: asset verification results
    else Non-Cloud
        App->>Store: setFolderPaths(folderPaths)
        Scanner->>Store: fetch per-model metadata (sizes) -> setFileSize(url,size)
    end
    App->>Panel: render error-overlay with grouped missing models
    Panel->>Downloader: user triggers "Download all"
    Downloader->>Store: read downloadable models & sizes
    Downloader->>Downloader: invoke per-model downloadModel calls
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇
I found the gaps where models hid,
Closed the dialog, brought a grid.
Sizes tallied, buttons gleam,
One click fetches every dream.
Hoppity-hop — downloads stream.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: surfacing missing models in Error Tab for OSS and removing legacy dialog components.
End-To-End Regression Coverage For Fixes ✅ Passed PR modifies multiple files under browser_tests/ including dialog.spec.ts and ComfyPage.ts, providing end-to-end regression test coverage for the feature changes.
Description check ✅ Passed The PR description comprehensively follows the template structure with Summary, Changes, and Test plan sections. It clearly articulates the changes made, breaking changes, and provides evidence of testing.

✏️ 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 feat/oss-missing-model-error-tab
📝 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 14, 2026

🎭 Playwright: ✅ 556 passed, 0 failed · 5 flaky

📊 Browser Reports
  • chromium: View Report (✅ 543 / ❌ 0 / ⚠️ 5 / ⏭️ 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)

@github-actions
Copy link

github-actions bot commented Mar 14, 2026

📦 Bundle: 4.99 MB gzip 🔴 +2.11 kB

Details

Summary

  • Raw size: 23 MB baseline 23 MB — 🔴 +1.04 kB
  • Gzip: 4.99 MB baseline 4.99 MB — 🔴 +2.11 kB
  • Brotli: 3.85 MB baseline 3.85 MB — 🔴 +2.9 kB
  • Bundles: 239 current • 235 baseline • 222 added / 218 removed

Category Glance
Data & Services 🟢 -27.8 kB (3.14 MB) · Utilities & Hooks 🔴 +15.4 kB (84.2 kB) · Other 🔴 +7.73 kB (8.21 MB) · Graph Workspace 🔴 +3.77 kB (1.09 MB) · Panels & Settings 🔴 +681 B (460 kB) · Views & Navigation 🔴 +486 B (75.7 kB) · + 5 more

App Entry Points — 21.9 kB (baseline 21.7 kB) • 🔴 +192 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CFGJW339.js (new) 21.9 kB 🔴 +21.9 kB 🔴 +7.78 kB 🔴 +6.72 kB
assets/index-7Rtbc0jP.js (removed) 21.7 kB 🟢 -21.7 kB 🟢 -7.71 kB 🟢 -6.63 kB

Status: 1 added / 1 removed

Graph Workspace — 1.09 MB (baseline 1.08 MB) • 🔴 +3.77 kB

Graph editor runtime, canvas, workflow orchestration

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

Status: 1 added / 1 removed

Views & Navigation — 75.7 kB (baseline 75.3 kB) • 🔴 +486 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-cX20n-H_.js (new) 15.6 kB 🔴 +15.6 kB 🔴 +3.38 kB 🔴 +2.88 kB
assets/CloudSurveyView-sNkFK6V9.js (removed) 15.6 kB 🟢 -15.6 kB 🟢 -3.38 kB 🟢 -2.88 kB
assets/CloudLoginView-DtMDt5Ke.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +3.3 kB 🔴 +2.92 kB
assets/CloudLoginView-BrMdUv0J.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.26 kB 🟢 -2.89 kB
assets/CloudSignupView-BtAa9oNq.js (new) 9.61 kB 🔴 +9.61 kB 🔴 +2.78 kB 🔴 +2.44 kB
assets/CloudSignupView-_hIKbial.js (removed) 9.52 kB 🟢 -9.52 kB 🟢 -2.75 kB 🟢 -2.42 kB
assets/UserCheckView-CVePHQlS.js (removed) 9.01 kB 🟢 -9.01 kB 🟢 -2.31 kB 🟢 -2.01 kB
assets/UserCheckView-CXridQaA.js (new) 9.01 kB 🔴 +9.01 kB 🔴 +2.31 kB 🔴 +2.01 kB
assets/CloudLayoutView-DfiyI0yw.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/CloudLayoutView-BrBYeFcM.js (removed) 7.3 kB 🟢 -7.3 kB 🟢 -2.27 kB 🟢 -1.96 kB
assets/CloudForgotPasswordView-e5Aj4vVH.js (new) 5.81 kB 🔴 +5.81 kB 🔴 +2.03 kB 🔴 +1.82 kB
assets/CloudForgotPasswordView-CKs1hg_7.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -2 kB 🟢 -1.77 kB
assets/CloudAuthTimeoutView-U28Ltl8_.js (new) 5.17 kB 🔴 +5.17 kB 🔴 +1.86 kB 🔴 +1.64 kB
assets/CloudAuthTimeoutView-Lh63T0Qx.js (removed) 5.09 kB 🟢 -5.09 kB 🟢 -1.83 kB 🟢 -1.59 kB
assets/CloudSubscriptionRedirectView-CIuZDzZe.js (new) 5.01 kB 🔴 +5.01 kB 🔴 +1.87 kB 🔴 +1.65 kB
assets/CloudSubscriptionRedirectView-DW4AroFt.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.84 kB 🟢 -1.62 kB
assets/UserSelectView-C6mmRaU7.js (new) 4.67 kB 🔴 +4.67 kB 🔴 +1.73 kB 🔴 +1.53 kB
assets/UserSelectView-PAU28XiI.js (removed) 4.67 kB 🟢 -4.67 kB 🟢 -1.73 kB 🟢 -1.53 kB
assets/CloudSorryContactSupportView-DVucrv0K.js (removed) 1.21 kB 🟢 -1.21 kB 🟢 -604 B 🟢 -528 B
assets/CloudSorryContactSupportView-fqeQ05j4.js (new) 1.21 kB 🔴 +1.21 kB 🔴 +606 B 🔴 +529 B
assets/layout-BE0wPzm2.js (removed) 385 B 🟢 -385 B 🟢 -271 B 🟢 -223 B
assets/layout-D0Bu-Rt0.js (new) 385 B 🔴 +385 B 🔴 +268 B 🔴 +218 B

Status: 11 added / 11 removed

Panels & Settings — 460 kB (baseline 460 kB) • 🔴 +681 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settings-B9Pwl29v.js (new) 38.8 kB 🔴 +38.8 kB 🔴 +9.45 kB 🔴 +7.79 kB
assets/settings-JqHYHJz0.js (removed) 38.8 kB 🟢 -38.8 kB 🟢 -9.45 kB 🟢 -7.76 kB
assets/settings-9QSSkTCa.js (new) 34.4 kB 🔴 +34.4 kB 🔴 +8.43 kB 🔴 +7.09 kB
assets/settings-CgIImzXZ.js (removed) 34.4 kB 🟢 -34.4 kB 🟢 -8.43 kB 🟢 -7.1 kB
assets/settings-BmOyNex4.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +8.24 kB 🔴 +6.74 kB
assets/settings-DcAJ3VeS.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -8.24 kB 🟢 -6.72 kB
assets/settings-BA8kOJiA.js (new) 30.7 kB 🔴 +30.7 kB 🔴 +8.55 kB 🔴 +7.16 kB
assets/settings-DPGGy1ee.js (removed) 30.7 kB 🟢 -30.7 kB 🟢 -8.55 kB 🟢 -7.15 kB
assets/settings-CyVE159k.js (removed) 30.1 kB 🟢 -30.1 kB 🟢 -8.19 kB 🟢 -7.17 kB
assets/settings-DeUk0--V.js (new) 30.1 kB 🔴 +30.1 kB 🔴 +8.19 kB 🔴 +7.17 kB
assets/settings-4x1YHshq.js (removed) 28.9 kB 🟢 -28.9 kB 🟢 -7.88 kB 🟢 -6.71 kB
assets/settings-B4EA-3pw.js (new) 28.9 kB 🔴 +28.9 kB 🔴 +7.88 kB 🔴 +6.7 kB
assets/KeybindingPanel-DCVQta47.js (new) 28.9 kB 🔴 +28.9 kB 🔴 +6.17 kB 🔴 +5.49 kB
assets/settings-9RehNOAa.js (removed) 28.9 kB 🟢 -28.9 kB 🟢 -8.1 kB 🟢 -7.08 kB
assets/settings-CLz_Hooa.js (new) 28.9 kB 🔴 +28.9 kB 🔴 +8.1 kB 🔴 +7.08 kB
assets/KeybindingPanel-Cz4qSSFY.js (removed) 28.8 kB 🟢 -28.8 kB 🟢 -6.14 kB 🟢 -5.45 kB
assets/settings-ChUKcC_S.js (removed) 28 kB 🟢 -28 kB 🟢 -7.8 kB 🟢 -6.78 kB
assets/settings-D0NP2oqy.js (new) 28 kB 🔴 +28 kB 🔴 +7.8 kB 🔴 +6.77 kB
assets/settings-DyMzlRFO.js (new) 27.9 kB 🔴 +27.9 kB 🔴 +8.23 kB 🔴 +6.86 kB
assets/settings-fYZUwZW3.js (removed) 27.9 kB 🟢 -27.9 kB 🟢 -8.23 kB 🟢 -6.85 kB
assets/settings-B8UriKLj.js (new) 24.6 kB 🔴 +24.6 kB 🔴 +7.99 kB 🔴 +6.45 kB
assets/settings-WgI_mXzx.js (removed) 24.6 kB 🟢 -24.6 kB 🟢 -7.99 kB 🟢 -6.44 kB
assets/settings-CFFWzLMp.js (new) 24 kB 🔴 +24 kB 🔴 +7.76 kB 🔴 +6.05 kB
assets/settings-DcxL5WJ9.js (removed) 24 kB 🟢 -24 kB 🟢 -7.77 kB 🟢 -6.05 kB
assets/SecretsPanel-BzEET2ML.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.41 kB 🔴 +4.75 kB
assets/SecretsPanel-D8zTM15S.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.41 kB 🟢 -4.75 kB
assets/LegacyCreditsPanel--ljwhNJW.js (new) 21.3 kB 🔴 +21.3 kB 🔴 +5.69 kB 🔴 +5.02 kB
assets/LegacyCreditsPanel-C89Egv1-.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -5.66 kB 🟢 -4.99 kB
assets/SubscriptionPanel-BSOABmR0.js (new) 19.3 kB 🔴 +19.3 kB 🔴 +4.87 kB 🔴 +4.29 kB
assets/SubscriptionPanel-SdiUosHp.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.83 kB 🟢 -4.25 kB
assets/AboutPanel-BUf-vTHm.js (new) 12 kB 🔴 +12 kB 🔴 +3.31 kB 🔴 +2.97 kB
assets/AboutPanel-BS1eNYqe.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -3.3 kB 🟢 -2.95 kB
assets/ExtensionPanel-CQcvw2ON.js (new) 9.63 kB 🔴 +9.63 kB 🔴 +2.74 kB 🔴 +2.43 kB
assets/ExtensionPanel-7zhVERhh.js (removed) 9.54 kB 🟢 -9.54 kB 🟢 -2.71 kB 🟢 -2.4 kB
assets/ServerConfigPanel-UBuFSbnk.js (new) 6.7 kB 🔴 +6.7 kB 🔴 +2.22 kB 🔴 +1.99 kB
assets/ServerConfigPanel-DQGnHGRB.js (removed) 6.62 kB 🟢 -6.62 kB 🟢 -2.19 kB 🟢 -1.95 kB
assets/UserPanel-Dkbsom9v.js (new) 6.41 kB 🔴 +6.41 kB 🔴 +2.08 kB 🔴 +1.83 kB
assets/UserPanel-Cp6j5J6m.js (removed) 6.33 kB 🟢 -6.33 kB 🟢 -2.05 kB 🟢 -1.8 kB
assets/config-BazmIiy7.js (removed) 1.79 kB 🟢 -1.79 kB 🟢 -865 B 🟢 -708 B
assets/config-mqWtMfCQ.js (new) 1.79 kB 🔴 +1.79 kB 🔴 +866 B 🔴 +714 B
assets/cloudRemoteConfig-Ckuy9vML.js (new) 1.7 kB 🔴 +1.7 kB 🔴 +830 B 🔴 +709 B
assets/cloudRemoteConfig-Dw9uExeo.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -800 B 🟢 -679 B
assets/refreshRemoteConfig-B49_wRup.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +646 B 🔴 +549 B
assets/refreshRemoteConfig-BoEPZ-Fy.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -648 B 🟢 -550 B

Status: 22 added / 22 removed

User & Accounts — 16.8 kB (baseline 16.6 kB) • 🔴 +162 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/PasswordFields-C7GvkX0Y.js (new) 4.68 kB 🔴 +4.68 kB 🔴 +1.42 kB 🔴 +1.25 kB
assets/PasswordFields-DcCUqFkG.js (removed) 4.68 kB 🟢 -4.68 kB 🟢 -1.42 kB 🟢 -1.25 kB
assets/auth-3DpWuodz.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/auth-BQvin6TR.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/SignUpForm-B5vNSrjW.js (removed) 3.18 kB 🟢 -3.18 kB 🟢 -1.29 kB 🟢 -1.15 kB
assets/SignUpForm-MpRcP6Fv.js (new) 3.18 kB 🔴 +3.18 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-QuZ6hb0f.js (new) 2.52 kB 🔴 +2.52 kB 🔴 +1.13 kB 🔴 +996 B
assets/UpdatePasswordContent-B-mFt-sf.js (removed) 2.44 kB 🟢 -2.44 kB 🟢 -1.09 kB 🟢 -965 B
assets/WorkspaceProfilePic-CVvFhrNa.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -861 B 🟢 -777 B
assets/WorkspaceProfilePic-DbdLnJjE.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +860 B 🔴 +779 B
assets/firebaseAuthStore-2r0hkIZd.js (new) 869 B 🔴 +869 B 🔴 +416 B 🔴 +369 B
assets/firebaseAuthStore-B8VgThyN.js (removed) 788 B 🟢 -788 B 🟢 -386 B 🟢 -343 B
assets/auth-BK7CE6WL.js (removed) 313 B 🟢 -313 B 🟢 -197 B 🟢 -184 B
assets/auth-D4wm9fMJ.js (new) 313 B 🔴 +313 B 🔴 +197 B 🔴 +172 B

Status: 7 added / 7 removed

Editors & Dialogs — 81.9 kB (baseline 81.8 kB) • 🔴 +170 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-CyH2gnzj.js (new) 81.1 kB 🔴 +81.1 kB 🔴 +16.9 kB 🔴 +14.5 kB
assets/useShareDialog-BnjLuCDV.js (removed) 81 kB 🟢 -81 kB 🟢 -16.9 kB 🟢 -14.4 kB
assets/useSubscriptionDialog-BAGa1aX_.js (new) 817 B 🔴 +817 B 🔴 +409 B 🔴 +352 B
assets/useSubscriptionDialog-D18KZm2I.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -328 B

Status: 2 added / 2 removed

UI Components — 59.2 kB (baseline 59 kB) • 🔴 +243 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-25uZZDGa.js (removed) 14.3 kB 🟢 -14.3 kB 🟢 -4 kB 🟢 -3.57 kB
assets/ComfyQueueButton-Fj9fJokN.js (new) 14.3 kB 🔴 +14.3 kB 🔴 +4.01 kB 🔴 +3.57 kB
assets/useTerminalTabs-C24gMYsc.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +3.53 kB 🔴 +3.1 kB
assets/useTerminalTabs-CcvTIaID.js (removed) 10.4 kB 🟢 -10.4 kB 🟢 -3.5 kB 🟢 -3.07 kB
assets/TopbarBadge-Bnm3BoM3.js (removed) 7.53 kB 🟢 -7.53 kB 🟢 -1.85 kB 🟢 -1.63 kB
assets/TopbarBadge-BZIfuUSE.js (new) 7.53 kB 🔴 +7.53 kB 🔴 +1.84 kB 🔴 +1.63 kB
assets/ScrubableNumberInput-CMFGgoZx.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/ScrubableNumberInput-Dx7LngHp.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -2.13 kB 🟢 -1.89 kB
assets/toggle-group-BVySyKd5.js (new) 4.03 kB 🔴 +4.03 kB 🔴 +1.41 kB 🔴 +1.25 kB
assets/toggle-group-By5E1G-i.js (removed) 4.03 kB 🟢 -4.03 kB 🟢 -1.41 kB 🟢 -1.25 kB
assets/FormSearchInput-A8IbMWuw.js (new) 3.94 kB 🔴 +3.94 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/FormSearchInput-BpEgMicB.js (removed) 3.94 kB 🟢 -3.94 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/Button-CfJrPYd9.js (new) 3.42 kB 🔴 +3.42 kB 🔴 +1.34 kB 🔴 +1.18 kB
assets/Button-CzCLfUsG.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -1.34 kB 🟢 -1.18 kB
assets/SubscribeButton-BHIDWmtN.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +916 B
assets/SubscribeButton-CmIlqL-b.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.04 kB 🟢 -916 B
assets/WidgetButton-C5HuExH9.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -950 B 🟢 -855 B
assets/WidgetButton-DL1OO9s-.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +950 B 🔴 +860 B
assets/cloudFeedbackTopbarButton-Czhgj-ef.js (new) 1.51 kB 🔴 +1.51 kB 🔴 +772 B 🔴 +697 B
assets/cloudFeedbackTopbarButton-CtzEr6fp.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -743 B 🟢 -658 B
assets/UserAvatar-B9fMikMO.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +651 B 🔴 +560 B
assets/UserAvatar-XBmTe5nW.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -650 B 🟢 -558 B
assets/CloudBadge-B8l4Ioh2.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -593 B 🟢 -519 B
assets/CloudBadge-BS8_u69b.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +594 B 🔴 +521 B
assets/ComfyQueueButton-DkIMOBNs.js (new) 874 B 🔴 +874 B 🔴 +423 B 🔴 +375 B
assets/ComfyQueueButton-B8zxtISX.js (removed) 793 B 🟢 -793 B 🟢 -391 B 🟢 -352 B

Status: 13 added / 13 removed

Data & Services — 3.14 MB (baseline 3.17 MB) • 🟢 -27.8 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-B3VoQifa.js (removed) 2.15 MB 🟢 -2.15 MB 🟢 -495 kB 🟢 -370 kB
assets/dialogService-DLC_wmQe.js (new) 2.14 MB 🔴 +2.14 MB 🔴 +491 kB 🔴 +368 kB
assets/api-h_ggKCRg.js (new) 862 kB 🔴 +862 kB 🔴 +206 kB 🔴 +162 kB
assets/api-D9np9BsD.js (removed) 862 kB 🟢 -862 kB 🟢 -206 kB 🟢 -162 kB
assets/load3dService-DMpDeXCp.js (new) 93.4 kB 🔴 +93.4 kB 🔴 +19.8 kB 🔴 +17 kB
assets/load3dService-DwgwFjj9.js (removed) 93.3 kB 🟢 -93.3 kB 🟢 -19.8 kB 🟢 -17 kB
assets/extensionStore-CWlTMZq8.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -6.7 kB 🟢 -5.86 kB
assets/workflowShareService-DFZmcEjh.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -4.32 kB 🟢 -3.8 kB
assets/workflowShareService-DN6JzyVj.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +4.32 kB 🔴 +3.8 kB
assets/releaseStore-CE05mLNm.js (new) 8.11 kB 🔴 +8.11 kB 🔴 +2.27 kB 🔴 +1.99 kB
assets/releaseStore-DKZb2s1b.js (removed) 8.07 kB 🟢 -8.07 kB 🟢 -2.26 kB 🟢 -1.98 kB
assets/keybindingService-BDZgshjc.js (removed) 6.99 kB 🟢 -6.99 kB 🟢 -1.74 kB 🟢 -1.49 kB
assets/keybindingService-CR1hMyde.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +1.73 kB 🔴 +1.49 kB
assets/extensionStore-BFJScJDi.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.68 kB 🔴 +1.44 kB
assets/serverConfigStore-BMw7voHC.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -810 B 🟢 -706 B
assets/serverConfigStore-DBKtWI6-.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +810 B 🔴 +705 B
assets/userStore-CqOAkQSW.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -870 B 🟢 -763 B
assets/userStore-DywwFnn7.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +869 B 🔴 +763 B
assets/bootstrapStore-B1NU0KFw.js (new) 2.11 kB 🔴 +2.11 kB 🔴 +890 B 🔴 +808 B
assets/bootstrapStore-Mb37uuQw.js (removed) 2.11 kB 🟢 -2.11 kB 🟢 -891 B 🟢 -806 B
assets/electronDownloadStore-BNV-_J3D.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +736 B 🔴 +652 B
assets/audioService-sH1nJ0YC.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +864 B 🔴 +746 B
assets/audioService-wruU5wFf.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -862 B 🟢 -741 B
assets/releaseStore-BdJJu78f.js (new) 841 B 🔴 +841 B 🔴 +415 B 🔴 +366 B
assets/workflowDraftStore-Fq6U8DaI.js (new) 817 B 🔴 +817 B 🔴 +408 B 🔴 +359 B
assets/dialogService-Bq2AKzDB.js (new) 806 B 🔴 +806 B 🔴 +400 B 🔴 +354 B
assets/settingStore-DtJyAcjk.js (new) 804 B 🔴 +804 B 🔴 +402 B 🔴 +350 B
assets/assetsStore-Dgq4Eoo2.js (new) 803 B 🔴 +803 B 🔴 +403 B 🔴 +354 B
assets/releaseStore-BIrYUMik.js (removed) 760 B 🟢 -760 B 🟢 -385 B 🟢 -343 B
assets/workflowDraftStore-DcCADoA_.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -337 B
assets/dialogService-BNgDJuFl.js (removed) 725 B 🟢 -725 B 🟢 -368 B 🟢 -329 B
assets/settingStore-e1Y5VmK0.js (removed) 723 B 🟢 -723 B 🟢 -373 B 🟢 -327 B
assets/assetsStore-Wjl3zQCr.js (removed) 722 B 🟢 -722 B 🟢 -371 B 🟢 -328 B

Status: 17 added / 16 removed

Utilities & Hooks — 84.2 kB (baseline 68.9 kB) • 🔴 +15.4 kB

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/formatUtil-f5qiufgt.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +5.25 kB 🔴 +4.59 kB
assets/useLoad3dViewer-_E8FNJdC.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -3.42 kB 🟢 -3.03 kB
assets/useLoad3dViewer-DUdgPR1N.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +3.42 kB 🔴 +3.03 kB
assets/useLoad3d-BJyqHmGZ.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.65 kB 🔴 +3.23 kB
assets/useLoad3d-CNICQQVF.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.65 kB 🟢 -3.23 kB
assets/colorUtil-BoDX6eKf.js (removed) 8.89 kB 🟢 -8.89 kB 🟢 -2.64 kB 🟢 -2.33 kB
assets/colorUtil-DVLSpp7Z.js (new) 8.89 kB 🔴 +8.89 kB 🔴 +2.64 kB 🔴 +2.33 kB
assets/useFeatureFlags-B3ZFrkeB.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.75 kB 🔴 +1.48 kB
assets/useFeatureFlags-BL7-OsBk.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.75 kB 🟢 -1.48 kB
assets/assetMetadataUtils-AhWGxT_I.js (new) 4.78 kB 🔴 +4.78 kB 🔴 +1.35 kB 🔴 +1.17 kB
assets/assetMetadataUtils-Cu1m4aen.js (removed) 4.78 kB 🟢 -4.78 kB 🟢 -1.35 kB 🟢 -1.17 kB
assets/useWorkspaceUI-BWuzb5by.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +978 B 🔴 +813 B
assets/useWorkspaceUI-g9iwbYDw.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -979 B 🟢 -813 B
assets/useExternalLink-_Rbq0GZz.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.17 kB 🟢 -1.04 kB
assets/useExternalLink-BmrSHjCA.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.17 kB 🔴 +1.04 kB
assets/subscriptionCheckoutUtil-897CcfZB.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.31 kB 🔴 +1.15 kB
assets/subscriptionCheckoutUtil-CLucc-HQ.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.31 kB 🟢 -1.15 kB
assets/useUpstreamValue-CBEK8a6Y.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +805 B 🔴 +718 B
assets/useUpstreamValue-CV0ceNN8.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -802 B 🟢 -721 B
assets/markdownRendererUtil-AGO63igr.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -830 B 🟢 -718 B
assets/markdownRendererUtil-Di1r-U2x.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +829 B 🔴 +716 B
assets/useErrorHandling-CIJ8WDKh.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +649 B 🔴 +552 B
assets/useErrorHandling-Jdqnw4P0.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -649 B 🟢 -556 B
assets/audioUtils-CgSj9FZL.js (removed) 958 B 🟢 -958 B 🟢 -564 B 🟢 -461 B
assets/audioUtils-xbJw7M5Z.js (new) 958 B 🔴 +958 B 🔴 +563 B 🔴 +455 B
assets/useLoad3d-Dtj3ksik.js (new) 940 B 🔴 +940 B 🔴 +454 B 🔴 +405 B
assets/useLoad3dViewer-BJsAeIGs.js (new) 919 B 🔴 +919 B 🔴 +439 B 🔴 +395 B
assets/useLoad3d-BF7_hYP2.js (removed) 859 B 🟢 -859 B 🟢 -425 B 🟢 -381 B
assets/useLoad3dViewer-BXiye1L0.js (removed) 838 B 🟢 -838 B 🟢 -408 B 🟢 -369 B
assets/useCurrentUser-B8fA6Pvs.js (new) 803 B 🔴 +803 B 🔴 +404 B 🔴 +352 B
assets/useWorkspaceSwitch-BKqDT0aE.js (removed) 747 B 🟢 -747 B 🟢 -383 B 🟢 -333 B
assets/useWorkspaceSwitch-zdvD1LAh.js (new) 747 B 🔴 +747 B 🔴 +387 B 🔴 +332 B
assets/useCurrentUser-DKtvKIDr.js (removed) 722 B 🟢 -722 B 🟢 -372 B 🟢 -329 B
assets/envUtil-B_R_XY7v.js (new) 489 B 🔴 +489 B 🔴 +311 B 🔴 +244 B
assets/envUtil-BOOGgtql.js (removed) 489 B 🟢 -489 B 🟢 -309 B 🟢 -244 B
assets/SkeletonUtils-BrLYgVOH.js (removed) 133 B 🟢 -133 B 🟢 -114 B 🟢 -109 B
assets/SkeletonUtils-D-ScwR1F.js (new) 133 B 🔴 +133 B 🔴 +114 B 🔴 +105 B
assets/_plugin-vue_export-helper-DqNI4win.js 365 B 365 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 19 added / 18 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-three-7fo2cFbZ.js (new) 1.83 MB 🔴 +1.83 MB 🔴 +394 kB 🔴 +287 kB
assets/vendor-three-D0iKHrgP.js (removed) 1.83 MB 🟢 -1.83 MB 🟢 -394 kB 🟢 -287 kB
assets/vendor-other-DD8n2cnE.js (removed) 1.76 MB 🟢 -1.76 MB 🟢 -362 kB 🟢 -288 kB
assets/vendor-other-DllqcebE.js (new) 1.76 MB 🔴 +1.76 MB 🔴 +362 kB 🔴 +287 kB
assets/vendor-primevue-Bm5ZkAS2.js (new) 1.75 MB 🔴 +1.75 MB 🔴 +316 kB 🔴 +191 kB
assets/vendor-primevue-DGRjWEoA.js (removed) 1.75 MB 🟢 -1.75 MB 🟢 -316 kB 🟢 -191 kB
assets/vendor-tiptap-bePjZBYs.js (removed) 737 kB 🟢 -737 kB 🟢 -182 kB 🟢 -146 kB
assets/vendor-tiptap-Dk5jn8en.js (new) 737 kB 🔴 +737 kB 🔴 +182 kB 🔴 +146 kB
assets/vendor-chart-5Q8F_9hS.js (new) 411 kB 🔴 +411 kB 🔴 +99.9 kB 🔴 +82.7 kB
assets/vendor-chart-QIvOlSgA.js (removed) 411 kB 🟢 -411 kB 🟢 -99.9 kB 🟢 -82.8 kB
assets/vendor-xterm-_VieD4Yp.js (new) 374 kB 🔴 +374 kB 🔴 +75.6 kB 🔴 +61.1 kB
assets/vendor-xterm-DZ7n4cB7.js (removed) 374 kB 🟢 -374 kB 🟢 -75.6 kB 🟢 -61.1 kB
assets/vendor-axios-B-zaJ78_.js 93 kB 93 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-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-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-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

Status: 6 added / 6 removed

Other — 8.21 MB (baseline 8.21 MB) • 🔴 +7.73 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-CignTtB3.js (new) 553 kB 🔴 +553 kB 🔴 +108 kB 🔴 +84.7 kB
assets/i18n-CfPFpEth.js (removed) 553 kB 🟢 -553 kB 🟢 -108 kB 🟢 -84.7 kB
assets/nodeDefs-BwxSR87L.js (removed) 500 kB 🟢 -500 kB 🟢 -76.2 kB 🟢 -52.5 kB
assets/nodeDefs-CaYvcS-e.js (new) 500 kB 🔴 +500 kB 🔴 +76.2 kB 🔴 +52.5 kB
assets/nodeDefs-C109fHHV.js (removed) 459 kB 🟢 -459 kB 🟢 -71.1 kB 🟢 -50.2 kB
assets/nodeDefs-TSWF2p3s.js (new) 459 kB 🔴 +459 kB 🔴 +71.1 kB 🔴 +50.2 kB
assets/nodeDefs-D84pIz-A.js (new) 458 kB 🔴 +458 kB 🔴 +68.8 kB 🔴 +48.4 kB
assets/nodeDefs-DGeylh_m.js (removed) 458 kB 🟢 -458 kB 🟢 -68.8 kB 🟢 -48.4 kB
assets/nodeDefs-BLiR_Cci.js (removed) 423 kB 🟢 -423 kB 🟢 -69.3 kB 🟢 -48.4 kB
assets/nodeDefs-D_PHcjIu.js (new) 423 kB 🔴 +423 kB 🔴 +69.3 kB 🔴 +48.4 kB
assets/nodeDefs-BHJWB8Hi.js (removed) 411 kB 🟢 -411 kB 🟢 -67.7 kB 🟢 -47.9 kB
assets/nodeDefs-C55R_xnH.js (new) 411 kB 🔴 +411 kB 🔴 +67.7 kB 🔴 +47.9 kB
assets/nodeDefs-Dimlxn51.js (removed) 407 kB 🟢 -407 kB 🟢 -66.3 kB 🟢 -48.5 kB
assets/nodeDefs-IO5Km4WB.js (new) 407 kB 🔴 +407 kB 🔴 +66.3 kB 🔴 +48.5 kB
assets/nodeDefs-CeR-W4r5.js (removed) 406 kB 🟢 -406 kB 🟢 -67.7 kB 🟢 -49.1 kB
assets/nodeDefs-CWB-5pw4.js (new) 406 kB 🔴 +406 kB 🔴 +67.7 kB 🔴 +49.1 kB
assets/nodeDefs-COCkXQbs.js (new) 403 kB 🔴 +403 kB 🔴 +64.8 kB 🔴 +47.3 kB
assets/nodeDefs-CYNwkBlA.js (removed) 403 kB 🟢 -403 kB 🟢 -64.8 kB 🟢 -47.3 kB
assets/nodeDefs-CEMVS_MF.js (new) 398 kB 🔴 +398 kB 🔴 +63.8 kB 🔴 +46.6 kB
assets/nodeDefs-DXcKksxR.js (removed) 398 kB 🟢 -398 kB 🟢 -63.8 kB 🟢 -46.6 kB
assets/nodeDefs-BhazHjGG.js (removed) 373 kB 🟢 -373 kB 🟢 -66.3 kB 🟢 -46.4 kB
assets/nodeDefs-gkfihp9l.js (new) 373 kB 🔴 +373 kB 🔴 +66.3 kB 🔴 +46.4 kB
assets/nodeDefs-C_IlCaTY.js (new) 369 kB 🔴 +369 kB 🔴 +65.4 kB 🔴 +45.3 kB
assets/nodeDefs-CVv25Zq-.js (removed) 369 kB 🟢 -369 kB 🟢 -65.4 kB 🟢 -45.3 kB
assets/main-DbpvTHsj.js (removed) 228 kB 🟢 -228 kB 🟢 -58.9 kB 🟢 -46.5 kB
assets/main-DQLi00Nb.js (new) 228 kB 🔴 +228 kB 🔴 +58.9 kB 🔴 +46.5 kB
assets/main-BIs1qLld.js (removed) 204 kB 🟢 -204 kB 🟢 -53.3 kB 🟢 -43 kB
assets/main-UzkNfxp0.js (new) 204 kB 🔴 +204 kB 🔴 +53.3 kB 🔴 +43 kB
assets/main-CF-tBW25.js (new) 196 kB 🔴 +196 kB 🔴 +53.1 kB 🔴 +42.4 kB
assets/main-Db-L7jAY.js (removed) 196 kB 🟢 -196 kB 🟢 -53.1 kB 🟢 -42.4 kB
assets/main-BUVqJDoB.js (removed) 187 kB 🟢 -187 kB 🟢 -52.7 kB 🟢 -42 kB
assets/main-COFLxTPI.js (new) 187 kB 🔴 +187 kB 🔴 +52.7 kB 🔴 +42 kB
assets/main-CO5hcLH6.js (removed) 171 kB 🟢 -171 kB 🟢 -51.2 kB 🟢 -42.9 kB
assets/main-O2P_jQQg.js (new) 171 kB 🔴 +171 kB 🔴 +51.2 kB 🔴 +42.9 kB
assets/main-Bauqfsmj.js (new) 168 kB 🔴 +168 kB 🔴 +50.6 kB 🔴 +40.9 kB
assets/main-CBpFsmCa.js (removed) 168 kB 🟢 -168 kB 🟢 -50.6 kB 🟢 -40.9 kB
assets/main-CduD3FWK.js (removed) 165 kB 🟢 -165 kB 🟢 -50.1 kB 🟢 -41.7 kB
assets/main-DOyeFl4y.js (new) 165 kB 🔴 +165 kB 🔴 +50.1 kB 🔴 +41.7 kB
assets/main-CamTRFWX.js (new) 163 kB 🔴 +163 kB 🔴 +50.3 kB 🔴 +42.3 kB
assets/main-ULS0BFNt.js (removed) 163 kB 🟢 -163 kB 🟢 -50.3 kB 🟢 -42.3 kB
assets/main-B3GpaXW6.js (new) 162 kB 🔴 +162 kB 🔴 +49.6 kB 🔴 +41.7 kB
assets/main-BYJEKs0c.js (removed) 162 kB 🟢 -162 kB 🟢 -49.6 kB 🟢 -41.7 kB
assets/main-_G20NYX6.js (new) 144 kB 🔴 +144 kB 🔴 +49.4 kB 🔴 +39.5 kB
assets/main-Cpo91t82.js (removed) 144 kB 🟢 -144 kB 🟢 -49.4 kB 🟢 -39.5 kB
assets/main-CE7GagrW.js (removed) 143 kB 🟢 -143 kB 🟢 -49.3 kB 🟢 -39.2 kB
assets/main-DvDUcM8m.js (new) 143 kB 🔴 +143 kB 🔴 +49.3 kB 🔴 +39.2 kB
assets/core-BiDIGAcG.js (new) 75.9 kB 🔴 +75.9 kB 🔴 +19.6 kB 🔴 +16.7 kB
assets/core-D3ay-fHb.js (removed) 75.7 kB 🟢 -75.7 kB 🟢 -19.5 kB 🟢 -16.7 kB
assets/groupNode-CyVOZeC1.js (new) 73.9 kB 🔴 +73.9 kB 🔴 +18.5 kB 🔴 +16.2 kB
assets/groupNode-D1Gzi_hH.js (removed) 73.9 kB 🟢 -73.9 kB 🟢 -18.5 kB 🟢 -16.3 kB
assets/WidgetSelect-BjjGnUDb.js (new) 63.2 kB 🔴 +63.2 kB 🔴 +13.8 kB 🔴 +11.9 kB
assets/WidgetSelect-DnyHqbdB.js (removed) 63.2 kB 🟢 -63.2 kB 🟢 -13.8 kB 🟢 -11.9 kB
assets/SubscriptionRequiredDialogContentWorkspace-xLOD6VXy.js (new) 47.1 kB 🔴 +47.1 kB 🔴 +8.78 kB 🔴 +7.59 kB
assets/SubscriptionRequiredDialogContentWorkspace-B1iSDifT.js (removed) 47.1 kB 🟢 -47.1 kB 🟢 -8.76 kB 🟢 -7.56 kB
assets/WidgetPainter-DBcNnwrY.js (new) 33.2 kB 🔴 +33.2 kB 🔴 +8.04 kB 🔴 +7.14 kB
assets/WidgetPainter-QyOFmKhT.js (removed) 33.1 kB 🟢 -33.1 kB 🟢 -8.01 kB 🟢 -7.1 kB
assets/Load3DControls-Bh8gT7iG.js (new) 32.1 kB 🔴 +32.1 kB 🔴 +5.47 kB 🔴 +4.77 kB
assets/Load3DControls-VruoUgwD.js (removed) 32.1 kB 🟢 -32.1 kB 🟢 -5.47 kB 🟢 -4.75 kB
assets/WorkspacePanelContent-yfCGSnwq.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +6.25 kB 🔴 +5.5 kB
assets/WorkspacePanelContent-BDK3tYRO.js (removed) 29.7 kB 🟢 -29.7 kB 🟢 -6.23 kB 🟢 -5.46 kB
assets/SubscriptionRequiredDialogContent-qkGIErNq.js (new) 26.1 kB 🔴 +26.1 kB 🔴 +6.64 kB 🔴 +5.88 kB
assets/SubscriptionRequiredDialogContent-CFuQApA7.js (removed) 26.1 kB 🟢 -26.1 kB 🟢 -6.62 kB 🟢 -5.83 kB
assets/Load3dViewerContent-ms0wjuuY.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -5.31 kB 🟢 -4.62 kB
assets/Load3dViewerContent-OU27cyil.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +5.31 kB 🔴 +4.62 kB
assets/WidgetImageCrop-B_Yv8sBu.js (new) 23.2 kB 🔴 +23.2 kB 🔴 +5.78 kB 🔴 +5.09 kB
assets/WidgetImageCrop-DnVYedQx.js (removed) 23.1 kB 🟢 -23.1 kB 🟢 -5.75 kB 🟢 -5.07 kB
assets/SubscriptionPanelContentWorkspace-BAbSW5X7.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.17 kB 🔴 +4.55 kB
assets/SubscriptionPanelContentWorkspace-CIaBL4I6.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.17 kB 🟢 -4.55 kB
assets/CurrentUserPopoverWorkspace-D1F5iZH6.js (new) 20.9 kB 🔴 +20.9 kB 🔴 +5.02 kB 🔴 +4.49 kB
assets/CurrentUserPopoverWorkspace-DZp5zgjX.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -4.99 kB 🟢 -4.46 kB
assets/SignInContent-G8nEGRFU.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.19 kB 🔴 +4.53 kB
assets/SignInContent-BRDS_c-k.js (removed) 20 kB 🟢 -20 kB 🟢 -5.16 kB 🟢 -4.49 kB
assets/WidgetInputNumber-DJoEFm5y.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.3 kB
assets/WidgetInputNumber-DuhUY6zs.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.29 kB
assets/commands-DC5sgzz0.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.11 kB 🟢 -3.2 kB
assets/commands-DXhyXTX3.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.11 kB 🔴 +3.19 kB
assets/WidgetRecordAudio-B9jZrZ6G.js (new) 18 kB 🔴 +18 kB 🔴 +5.13 kB 🔴 +4.59 kB
assets/WidgetRecordAudio-BPsWqoLy.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -5.09 kB 🟢 -4.57 kB
assets/commands-D6U7YFhN.js (removed) 17.8 kB 🟢 -17.8 kB 🟢 -3.79 kB 🟢 -2.93 kB
assets/commands-DKW4HmKH.js (new) 17.8 kB 🔴 +17.8 kB 🔴 +3.79 kB 🔴 +2.94 kB
assets/commands-BgfvFMt0.js (new) 17.8 kB 🔴 +17.8 kB 🔴 +3.85 kB 🔴 +3.03 kB
assets/commands-Bp4kggpz.js (removed) 17.8 kB 🟢 -17.8 kB 🟢 -3.85 kB 🟢 -3.03 kB
assets/commands-Bm9EUr3J.js (removed) 17.2 kB 🟢 -17.2 kB 🟢 -3.88 kB 🟢 -3.05 kB
assets/commands-D4UhEqHD.js (new) 17.2 kB 🔴 +17.2 kB 🔴 +3.88 kB 🔴 +3.05 kB
assets/commands-6C7uscef.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -3.61 kB 🟢 -3.01 kB
assets/commands-B1YxbAAl.js (new) 16.9 kB 🔴 +16.9 kB 🔴 +3.61 kB 🔴 +3 kB
assets/Load3D-Bc5Y5WzJ.js (removed) 16.8 kB 🟢 -16.8 kB 🟢 -4.12 kB 🟢 -3.59 kB
assets/Load3D-CsB8_dEj.js (new) 16.8 kB 🔴 +16.8 kB 🔴 +4.11 kB 🔴 +3.59 kB
assets/commands-CSGg3k5v.js (new) 16.3 kB 🔴 +16.3 kB 🔴 +3.6 kB 🔴 +2.96 kB
assets/commands-Dm1AG3mI.js (removed) 16.3 kB 🟢 -16.3 kB 🟢 -3.6 kB 🟢 -2.96 kB
assets/commands-BRWVNs15.js (new) 16.3 kB 🔴 +16.3 kB 🔴 +3.5 kB 🔴 +2.86 kB
assets/commands-CfrqwZHT.js (removed) 16.3 kB 🟢 -16.3 kB 🟢 -3.5 kB 🟢 -2.86 kB
assets/commands-CKIuqKev.js (new) 16.3 kB 🔴 +16.3 kB 🔴 +3.47 kB 🔴 +2.88 kB
assets/commands-pcbFneET.js (removed) 16.3 kB 🟢 -16.3 kB 🟢 -3.47 kB 🟢 -2.87 kB
assets/WidgetColorPicker-7LajmWlG.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -3.99 kB 🟢 -3.56 kB
assets/WidgetColorPicker-BdcBT55F.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +3.99 kB 🔴 +3.56 kB
assets/commands-B0dAi43J.js (removed) 16.1 kB 🟢 -16.1 kB 🟢 -3.74 kB 🟢 -2.92 kB
assets/commands-Bh9JYwnU.js (new) 16.1 kB 🔴 +16.1 kB 🔴 +3.74 kB 🔴 +2.92 kB
assets/commands-CD22-YFx.js (new) 15.4 kB 🔴 +15.4 kB 🔴 +3.67 kB 🔴 +2.77 kB
assets/commands-CWNMjnpx.js (removed) 15.4 kB 🟢 -15.4 kB 🟢 -3.67 kB 🟢 -2.76 kB
assets/commands-aHWGoXwC.js (removed) 15.3 kB 🟢 -15.3 kB 🟢 -3.63 kB 🟢 -2.71 kB
assets/commands-DEyEeqxL.js (new) 15.3 kB 🔴 +15.3 kB 🔴 +3.63 kB 🔴 +2.71 kB
assets/load3d-8etGiCDV.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.24 kB 🔴 +3.67 kB
assets/load3d-LZV_d9kY.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.21 kB 🟢 -3.65 kB
assets/WidgetCurve-B5WhuAb0.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.93 kB 🔴 +3.53 kB
assets/WidgetCurve-DJCmdXjb.js (removed) 11.7 kB 🟢 -11.7 kB 🟢 -3.9 kB 🟢 -3.51 kB
assets/AudioPreviewPlayer-BX2O2gaA.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.33 kB 🔴 +2.98 kB
assets/AudioPreviewPlayer-D_vOjiTP.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.3 kB 🟢 -2.96 kB
assets/SelectValue-BkB8Nehs.js (removed) 9.8 kB 🟢 -9.8 kB 🟢 -2.45 kB 🟢 -2.17 kB
assets/SelectValue-CSKcLDnn.js (new) 9.8 kB 🔴 +9.8 kB 🔴 +2.45 kB 🔴 +2.16 kB
assets/NightlySurveyController-BJ7Yfxt0.js (removed) 9.5 kB 🟢 -9.5 kB 🟢 -3.05 kB 🟢 -2.67 kB
assets/NightlySurveyController-zH2sn5x9.js (new) 9.5 kB 🔴 +9.5 kB 🔴 +3.05 kB 🔴 +2.66 kB
assets/nodeTemplates-x7sNNv_g.js (new) 9.41 kB 🔴 +9.41 kB 🔴 +3.31 kB 🔴 +2.91 kB
assets/nodeTemplates-DEjqtKC3.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -3.28 kB 🟢 -2.88 kB
assets/WidgetImageCompare-DRrbKltR.js (removed) 7.78 kB 🟢 -7.78 kB 🟢 -2.26 kB 🟢 -1.98 kB
assets/WidgetImageCompare-Du5HZfqN.js (new) 7.78 kB 🔴 +7.78 kB 🔴 +2.27 kB 🔴 +1.98 kB
assets/InviteMemberDialogContent-455yl69f.js (new) 7.62 kB 🔴 +7.62 kB 🔴 +2.38 kB 🔴 +2.08 kB
assets/InviteMemberDialogContent-DyE7a54z.js (removed) 7.53 kB 🟢 -7.53 kB 🟢 -2.36 kB 🟢 -2.07 kB
assets/Load3DConfiguration-COEroI7h.js (new) 6.55 kB 🔴 +6.55 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/Load3DConfiguration-Cvc1_oqG.js (removed) 6.55 kB 🟢 -6.55 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/onboardingCloudRoutes-DWj_AB4U.js (new) 6.26 kB 🔴 +6.26 kB 🔴 +1.95 kB 🔴 +1.69 kB
assets/onboardingCloudRoutes-D1-7XqnO.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/WidgetWithControl-BvieUkJA.js (new) 5.83 kB 🔴 +5.83 kB 🔴 +2.28 kB 🔴 +2.05 kB
assets/CreateWorkspaceDialogContent-Dspj0HVx.js (new) 5.79 kB 🔴 +5.79 kB 🔴 +2.08 kB 🔴 +1.81 kB
assets/WidgetWithControl-NpRtxPgz.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -2.25 kB 🟢 -2.01 kB
assets/CreateWorkspaceDialogContent-D0SSpOQ-.js (removed) 5.71 kB 🟢 -5.71 kB 🟢 -2.05 kB 🟢 -1.78 kB
assets/FreeTierDialogContent-DcpxyI0S.js (new) 5.62 kB 🔴 +5.62 kB 🔴 +1.97 kB 🔴 +1.74 kB
assets/EditWorkspaceDialogContent-DZ0BX5jw.js (new) 5.59 kB 🔴 +5.59 kB 🔴 +2.04 kB 🔴 +1.78 kB
assets/FreeTierDialogContent-BTNqYLTg.js (removed) 5.54 kB 🟢 -5.54 kB 🟢 -1.94 kB 🟢 -1.7 kB
assets/EditWorkspaceDialogContent-DTZn3EPT.js (removed) 5.51 kB 🟢 -5.51 kB 🟢 -2.01 kB 🟢 -1.76 kB
assets/ValueControlPopover-lXOs2zWH.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.85 kB 🔴 +1.65 kB
assets/Preview3d-BjJTu6C9.js (new) 5.16 kB 🔴 +5.16 kB 🔴 +1.7 kB 🔴 +1.48 kB
assets/WidgetTextarea-CIQhrl8O.js (new) 5.11 kB 🔴 +5.11 kB 🔴 +2 kB 🔴 +1.77 kB
assets/ValueControlPopover-CWM96kaC.js (removed) 5.1 kB 🟢 -5.1 kB 🟢 -1.82 kB 🟢 -1.63 kB
assets/Preview3d-BLkdn9t3.js (removed) 5.08 kB 🟢 -5.08 kB 🟢 -1.67 kB 🟢 -1.45 kB
assets/CancelSubscriptionDialogContent-BGrMgpmP.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/WidgetTextarea-CmpDDrDL.js (removed) 5.03 kB 🟢 -5.03 kB 🟢 -1.96 kB 🟢 -1.76 kB
assets/CancelSubscriptionDialogContent-B-Wtynjt.js (removed) 4.98 kB 🟢 -4.98 kB 🟢 -1.85 kB 🟢 -1.61 kB
assets/AnimationControls-bezowaZd.js (new) 4.78 kB 🔴 +4.78 kB 🔴 +1.66 kB 🔴 +1.47 kB
assets/AnimationControls-D_ssPKpl.js (removed) 4.78 kB 🟢 -4.78 kB 🟢 -1.66 kB 🟢 -1.46 kB
assets/DeleteWorkspaceDialogContent-DElvXE9p.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.72 kB 🔴 +1.49 kB
assets/tierBenefits-C6oRZi_w.js (new) 4.47 kB 🔴 +4.47 kB 🔴 +1.58 kB 🔴 +1.36 kB
assets/tierBenefits-GGuxPrMo.js (removed) 4.47 kB 🟢 -4.47 kB 🟢 -1.58 kB 🟢 -1.36 kB
assets/DeleteWorkspaceDialogContent-D_D__MOL.js (removed) 4.41 kB 🟢 -4.41 kB 🟢 -1.69 kB 🟢 -1.47 kB
assets/LeaveWorkspaceDialogContent-C-FcvXLm.js (new) 4.32 kB 🔴 +4.32 kB 🔴 +1.67 kB 🔴 +1.45 kB
assets/RemoveMemberDialogContent-CqiJfEOo.js (new) 4.3 kB 🔴 +4.3 kB 🔴 +1.62 kB 🔴 +1.42 kB
assets/LeaveWorkspaceDialogContent-546TzFui.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.64 kB 🟢 -1.42 kB
assets/RemoveMemberDialogContent-_Y7u4rGE.js (removed) 4.22 kB 🟢 -4.22 kB 🟢 -1.59 kB 🟢 -1.39 kB
assets/RevokeInviteDialogContent-B_VjuTqN.js (new) 4.21 kB 🔴 +4.21 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/RevokeInviteDialogContent-DYQznLPK.js (removed) 4.13 kB 🟢 -4.13 kB 🟢 -1.6 kB 🟢 -1.4 kB
assets/InviteMemberUpsellDialogContent-C7cLHVqJ.js (new) 4.11 kB 🔴 +4.11 kB 🔴 +1.49 kB 🔴 +1.31 kB
assets/missingModelDownload-CDZlvrCp.js (new) 4.05 kB 🔴 +4.05 kB 🔴 +1.59 kB 🔴 +1.39 kB
assets/InviteMemberUpsellDialogContent-B_thwsMp.js (removed) 4.03 kB 🟢 -4.03 kB 🟢 -1.46 kB 🟢 -1.28 kB
assets/cloudSessionCookie-COwherSD.js (new) 3.98 kB 🔴 +3.98 kB 🔴 +1.41 kB 🔴 +1.23 kB
assets/cloudSessionCookie-RiM9Mn4J.js (removed) 3.9 kB 🟢 -3.9 kB 🟢 -1.38 kB 🟢 -1.21 kB
assets/WidgetGalleria-BP4f8SEB.js (removed) 3.8 kB 🟢 -3.8 kB 🟢 -1.47 kB 🟢 -1.31 kB
assets/WidgetGalleria-BP74JvEw.js (new) 3.8 kB 🔴 +3.8 kB 🔴 +1.48 kB 🔴 +1.31 kB
assets/Popover-BXCaujAi.js (new) 3.77 kB 🔴 +3.77 kB 🔴 +1.49 kB 🔴 +1.33 kB
assets/Popover-xbED3XYT.js (removed) 3.77 kB 🟢 -3.77 kB 🟢 -1.49 kB 🟢 -1.32 kB
assets/WidgetToggleSwitch-Bwh6qUVP.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.43 kB 🔴 +1.26 kB
assets/WidgetToggleSwitch-CMqqTQxu.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.43 kB 🟢 -1.26 kB
assets/WidgetBoundingBox-BR7_6Tzc.js (removed) 3.62 kB 🟢 -3.62 kB 🟢 -1.01 kB 🟢 -884 B
assets/WidgetBoundingBox-GgFcQfOn.js (new) 3.62 kB 🔴 +3.62 kB 🔴 +1.01 kB 🔴 +886 B
assets/Slider-BYG1u0oC.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/Slider-D198AJYP.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.38 kB 🔴 +1.2 kB
assets/saveMesh-D_jjurLd.js (new) 3.5 kB 🔴 +3.5 kB 🔴 +1.5 kB 🔴 +1.32 kB
assets/saveMesh-CcRUvG7J.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -1.47 kB 🟢 -1.3 kB
assets/WidgetMarkdown-CifbFnvo.js (removed) 3.13 kB 🟢 -3.13 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/WidgetMarkdown-DXPuE1xE.js (new) 3.13 kB 🔴 +3.13 kB 🔴 +1.3 kB 🔴 +1.14 kB
assets/WidgetInputText-BKkdW4B1.js (new) 3.09 kB 🔴 +3.09 kB 🔴 +1.31 kB 🔴 +1.18 kB
assets/WidgetInputText-DZD-PABh.js (removed) 3.09 kB 🟢 -3.09 kB 🟢 -1.31 kB 🟢 -1.18 kB
assets/GlobalToast-B78tx-8p.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/GlobalToast-DpPXfz6z.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/MediaVideoTop-DKoWz3mq.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -1.2 kB 🟢 -1.05 kB
assets/MediaVideoTop-eDWtQoSO.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +1.19 kB 🔴 +1.08 kB
assets/ApiNodesSignInContent-122DuhFE.js (removed) 2.86 kB 🟢 -2.86 kB 🟢 -1.1 kB 🟢 -966 B
assets/ApiNodesSignInContent-C_5nHL8w.js (new) 2.86 kB 🔴 +2.86 kB 🔴 +1.1 kB 🔴 +973 B
assets/WidgetChart-Br2tE61m.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1.02 kB 🟢 -885 B
assets/WidgetChart-DD71Vf1E.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1.03 kB 🔴 +885 B
assets/WidgetLayoutField-BfWyH3b-.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.03 kB 🔴 +905 B
assets/WidgetLayoutField-DQtrhTV6.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.03 kB 🟢 -944 B
assets/SubscriptionBenefits-CecH6qXS.js (removed) 2.28 kB 🟢 -2.28 kB 🟢 -778 B 🟢 -657 B
assets/SubscriptionBenefits-DKpgoSPJ.js (new) 2.28 kB 🔴 +2.28 kB 🔴 +778 B 🔴 +659 B
assets/SubscribeToRun-BYKHjWFL.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +980 B 🔴 +870 B
assets/SubscribeToRun-q1GQ4MMG.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -976 B 🟢 -871 B
assets/MediaImageTop-B3Ndzn8y.js (removed) 2.02 kB 🟢 -2.02 kB 🟢 -982 B 🟢 -849 B
assets/MediaImageTop-B8bUltoc.js (new) 2.02 kB 🔴 +2.02 kB 🔴 +983 B 🔴 +872 B
assets/Media3DTop-lR1JiQqq.js (removed) 1.98 kB 🟢 -1.98 kB 🟢 -960 B 🟢 -816 B
assets/Media3DTop-yc5aD_uG.js (new) 1.98 kB 🔴 +1.98 kB 🔴 +957 B 🔴 +821 B
assets/BaseViewTemplate-BO4bu8SW.js (removed) 1.92 kB 🟢 -1.92 kB 🟢 -979 B 🟢 -872 B
assets/BaseViewTemplate-CoDEVLFQ.js (new) 1.92 kB 🔴 +1.92 kB 🔴 +980 B 🔴 +872 B
assets/CloudRunButtonWrapper-D-gsi2OQ.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +843 B 🔴 +778 B
assets/CloudRunButtonWrapper-Dm7zOsr5.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -810 B 🟢 -752 B
assets/auto-Da_dLKSr.js (removed) 1.7 kB 🟢 -1.7 kB 🟢 -619 B 🟢 -545 B
assets/auto-DVZM9Hkc.js (new) 1.7 kB 🔴 +1.7 kB 🔴 +622 B 🔴 +551 B
assets/cloudBadges-DCREpb_-.js (new) 1.61 kB 🔴 +1.61 kB 🔴 +818 B 🔴 +729 B
assets/MediaAudioTop-DDwe88_i.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -823 B 🟢 -684 B
assets/MediaAudioTop-DIuvS82I.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +825 B 🔴 +683 B
assets/signInSchema-Cp3pGVrA.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -578 B 🟢 -512 B
assets/signInSchema-Dyb0p5qi.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +577 B 🔴 +508 B
assets/cloudBadges-BJ3QCzVU.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -789 B 🟢 -691 B
assets/previousFullPath-5Aabz3L9.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +694 B 🔴 +599 B
assets/previousFullPath-DbiidDL2.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -692 B 🟢 -599 B
assets/cloudSubscription-CaQmijl1.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +743 B 🔴 +630 B
assets/widgetPropFilter-U-tRpxOF.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -703 B 🟢 -603 B
assets/widgetPropFilter-XFbYXzAf.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +704 B 🔴 +572 B
assets/VideoPlayOverlay-BEMiTJfJ.js (removed) 1.51 kB 🟢 -1.51 kB 🟢 -764 B 🟢 -673 B
assets/VideoPlayOverlay-FL3qajR5.js (new) 1.51 kB 🔴 +1.51 kB 🔴 +762 B 🔴 +673 B
assets/cloudSubscription-DOnGwA_i.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -709 B 🟢 -606 B
assets/Textarea-BCdbAQYh.js (removed) 1.42 kB 🟢 -1.42 kB 🟢 -744 B 🟢 -652 B
assets/Textarea-CfLZu4GT.js (new) 1.42 kB 🔴 +1.42 kB 🔴 +741 B 🔴 +649 B
assets/Loader-BAvtaqwA.js (new) 1.26 kB 🔴 +1.26 kB 🔴 +684 B 🔴 +595 B
assets/Loader-D9NTeFNU.js (removed) 1.26 kB 🟢 -1.26 kB 🟢 -685 B 🟢 -596 B
assets/Load3D-C6nyEA8q.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +525 B 🔴 +466 B
assets/nightlyBadges-C1KLzF9s.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +587 B 🔴 +514 B
assets/Load3dViewerContent-nA3nRUKY.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +498 B 🔴 +444 B
assets/Load3D-B6SilScy.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -496 B 🟢 -442 B
assets/MediaOtherTop-DLyNtqwJ.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -602 B 🟢 -499 B
assets/MediaOtherTop-E47jEBXy.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +601 B 🔴 +502 B
assets/nightlyBadges-DTdGXv50.js (removed) 1.06 kB 🟢 -1.06 kB 🟢 -554 B 🟢 -495 B
assets/MediaTextTop-BkPIHAIK.js (new) 1.06 kB 🔴 +1.06 kB 🔴 +598 B 🔴 +500 B
assets/MediaTextTop-BPdj4Cu9.js (removed) 1.06 kB 🟢 -1.06 kB 🟢 -597 B 🟢 -498 B
assets/SubscriptionPanelContentWorkspace-Cq8hWFYi.js (new) 1 kB 🔴 +1 kB 🔴 +468 B 🔴 +405 B
assets/Load3dViewerContent-YD60mFH1.js (removed) 993 B 🟢 -993 B 🟢 -467 B 🟢 -419 B
assets/ComfyOrgHeader-B5_02-pL.js (removed) 960 B 🟢 -960 B 🟢 -526 B 🟢 -463 B
assets/ComfyOrgHeader-DjEVw9y6.js (new) 960 B 🔴 +960 B 🔴 +528 B 🔴 +459 B
assets/SubscriptionPanelContentWorkspace-DFP5uGxg.js (removed) 920 B 🟢 -920 B 🟢 -437 B 🟢 -379 B
assets/WidgetLegacy-n8bUnvCh.js (new) 825 B 🔴 +825 B 🔴 +415 B 🔴 +357 B
assets/graphHasMissingNodes-BqistESI.js (removed) 822 B 🟢 -822 B 🟢 -412 B 🟢 -348 B
assets/graphHasMissingNodes-We1E3thd.js (new) 822 B 🔴 +822 B 🔴 +414 B 🔴 +347 B
assets/changeTracker-BZ-5Uh_8.js (new) 801 B 🔴 +801 B 🔴 +403 B 🔴 +354 B
assets/constants-Bh1Ukzcs.js (new) 766 B 🔴 +766 B 🔴 +374 B 🔴 +305 B
assets/constants-CDJrbKEX.js (removed) 766 B 🟢 -766 B 🟢 -370 B 🟢 -305 B
assets/WidgetLegacy-DzY5nXif.js (removed) 744 B 🟢 -744 B 🟢 -384 B 🟢 -339 B
assets/changeTracker-DKt9qCWs.js (removed) 720 B 🟢 -720 B 🟢 -373 B 🟢 -328 B
assets/src-C5oPOZUH.js (removed) 290 B 🟢 -290 B 🟢 -239 B 🟢 -209 B
assets/src-CAUfc4gs.js (new) 290 B 🔴 +290 B 🔴 +235 B 🔴 +208 B
assets/WidgetBoundingBox-C2TkJ416.js (new) 283 B 🔴 +283 B 🔴 +183 B 🔴 +159 B
assets/WidgetBoundingBox-C7hcUvUl.js (removed) 283 B 🟢 -283 B 🟢 -185 B 🟢 -163 B
assets/comfy-logo-single--m8ho-x0.js (removed) 272 B 🟢 -272 B 🟢 -183 B 🟢 -151 B
assets/comfy-logo-single-Dyos5lPW.js (new) 272 B 🔴 +272 B 🔴 +184 B 🔴 +148 B
assets/missingModelDownload-DPbku2pv.js (new) 267 B 🔴 +267 B 🔴 +183 B 🔴 +178 B
assets/i18n-BUdClz71.js (new) 137 B 🔴 +137 B 🔴 +122 B 🔴 +110 B
assets/i18n-E31GmVX_.js (removed) 137 B 🟢 -137 B 🟢 -122 B 🟢 -109 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 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/telemetry-7ZMuZPoG.js 443 B 443 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-BqIM6TDt.js 313 B 313 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DloL8--t.js 3.5 kB 3.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-ju3asrao.js 416 B 416 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 123 added / 121 removed

@github-actions
Copy link

github-actions bot commented Mar 14, 2026

⚡ Performance Report

⚠️ 11 regressions detected

Metric Baseline PR (n=3) Δ Sig
canvas-idle: event listeners 11 30 +173% ⚠️ z=5.9
canvas-mouse-sweep: event listeners 10 30 +190% ⚠️ z=9.0
dom-widget-clipping: style recalcs 14 15 +7% ⚠️ z=4.0
large-graph-idle: task duration 506ms 669ms +32% ⚠️ z=17.7
large-graph-idle: script duration 99ms 125ms +26% ⚠️ z=6.3
large-graph-idle: event listeners 24 44 +86% ⚠️ z=5.2
large-graph-pan: task duration 995ms 1202ms +21% ⚠️ z=45.9
large-graph-pan: script duration 387ms 442ms +14% ⚠️ z=189.4
subgraph-dom-widget-clipping: event listeners 14 33 +144% ⚠️ z=4.9
subgraph-idle: task duration 342ms 451ms +32% ⚠️ z=2.0
subgraph-mouse-sweep: event listeners 10 36 +252% ⚠️ z=66.4
All metrics
Metric Baseline PR (n=3) Δ Sig
canvas-idle: style recalcs 12 12 -3% z=-0.2
canvas-idle: layouts 0 0 -100% variance too high
canvas-idle: task duration 390ms 465ms +19% z=1.8
canvas-idle: DOM nodes 37 26 -31% z=-0.8
canvas-idle: script duration 24ms 32ms +35% z=1.5
canvas-idle: event listeners 11 30 +173% ⚠️ z=5.9
canvas-mouse-sweep: style recalcs 76 81 +6% z=-0.0
canvas-mouse-sweep: layouts 12 12 -3% z=-0.6
canvas-mouse-sweep: task duration 806ms 965ms +20% z=0.3
canvas-mouse-sweep: DOM nodes 75 67 -11% z=-0.7
canvas-mouse-sweep: script duration 131ms 145ms +11% z=0.5
canvas-mouse-sweep: event listeners 10 30 +190% ⚠️ z=9.0
dom-widget-clipping: style recalcs 14 15 +7% ⚠️ z=4.0
dom-widget-clipping: layouts 0 0 variance too high
dom-widget-clipping: task duration 346ms 410ms +18% z=1.4
dom-widget-clipping: DOM nodes 22 40 +81% z=1.6
dom-widget-clipping: script duration 64ms 75ms +17% z=1.0
dom-widget-clipping: event listeners 2 33 +1550% variance too high
large-graph-idle: style recalcs 14 14 +0% z=0.7
large-graph-idle: layouts 1 1 -33% z=-0.7
large-graph-idle: task duration 506ms 669ms +32% ⚠️ z=17.7
large-graph-idle: DOM nodes 69 57 -17% z=-0.3
large-graph-idle: script duration 99ms 125ms +26% ⚠️ z=6.3
large-graph-idle: event listeners 24 44 +86% ⚠️ z=5.2
large-graph-pan: style recalcs 71 70 -1% z=-2.1
large-graph-pan: layouts 0 0 +0%
large-graph-pan: task duration 995ms 1202ms +21% ⚠️ z=45.9
large-graph-pan: DOM nodes 21 19 -9% z=-4.9
large-graph-pan: script duration 387ms 442ms +14% ⚠️ z=189.4
large-graph-pan: event listeners 6 4 -33%
subgraph-dom-widget-clipping: style recalcs 50 49 -3% z=-2.9
subgraph-dom-widget-clipping: layouts 0 0 -100% z=-2.9
subgraph-dom-widget-clipping: task duration 353ms 416ms +18% z=1.5
subgraph-dom-widget-clipping: DOM nodes 39 25 -35% z=-2.8
subgraph-dom-widget-clipping: script duration 118ms 133ms +13% z=1.7
subgraph-dom-widget-clipping: event listeners 14 33 +144% ⚠️ z=4.9
subgraph-idle: style recalcs 14 12 -15% z=-0.6
subgraph-idle: layouts 1 0 -100% variance too high
subgraph-idle: task duration 342ms 451ms +32% ⚠️ z=2.0
subgraph-idle: DOM nodes 53 26 -52% z=-0.8
subgraph-idle: script duration 17ms 26ms +54% z=1.7
subgraph-idle: event listeners 17 31 +77% variance too high
subgraph-mouse-sweep: style recalcs 78 84 +7% z=1.1
subgraph-mouse-sweep: layouts 16 16 +0%
subgraph-mouse-sweep: task duration 666ms 909ms +37% z=1.0
subgraph-mouse-sweep: DOM nodes 79 88 +12% z=1.7
subgraph-mouse-sweep: script duration 97ms 110ms +13% z=0.7
subgraph-mouse-sweep: event listeners 10 36 +252% ⚠️ z=66.4
Historical variance (last 3 runs)
Metric μ σ CV
canvas-idle: style recalcs 12 1 5.9%
canvas-idle: layouts 0 0 86.6%
canvas-idle: task duration 397ms 39ms 9.7%
canvas-idle: DOM nodes 32 8 26.4%
canvas-idle: script duration 25ms 4ms 17.2%
canvas-idle: event listeners 9 3 37.3%
canvas-mouse-sweep: style recalcs 81 4 5.2%
canvas-mouse-sweep: layouts 12 0 1.6%
canvas-mouse-sweep: task duration 930ms 127ms 13.7%
canvas-mouse-sweep: DOM nodes 70 4 6.4%
canvas-mouse-sweep: script duration 140ms 11ms 7.8%
canvas-mouse-sweep: event listeners 7 3 33.6%
dom-widget-clipping: style recalcs 14 0 1.4%
dom-widget-clipping: layouts 0 0 173.2%
dom-widget-clipping: task duration 363ms 32ms 8.9%
dom-widget-clipping: DOM nodes 28 8 29.7%
dom-widget-clipping: script duration 68ms 7ms 9.5%
dom-widget-clipping: event listeners 4 3 84.1%
large-graph-idle: style recalcs 14 1 5.2%
large-graph-idle: layouts 1 0 28.3%
large-graph-idle: task duration 500ms 10ms 1.9%
large-graph-idle: DOM nodes 61 11 18.5%
large-graph-idle: script duration 96ms 5ms 4.7%
large-graph-idle: event listeners 21 4 21.8%
large-graph-pan: style recalcs 71 0 0.3%
large-graph-pan: layouts 0 0 0.0%
large-graph-pan: task duration 998ms 4ms 0.4%
large-graph-pan: DOM nodes 22 0 2.2%
large-graph-pan: script duration 387ms 0ms 0.1%
large-graph-pan: event listeners 6 0 0.0%
subgraph-dom-widget-clipping: style recalcs 50 1 1.1%
subgraph-dom-widget-clipping: layouts 1 0 34.6%
subgraph-dom-widget-clipping: task duration 378ms 26ms 6.9%
subgraph-dom-widget-clipping: DOM nodes 48 8 16.9%
subgraph-dom-widget-clipping: script duration 124ms 6ms 4.5%
subgraph-dom-widget-clipping: event listeners 17 3 18.8%
subgraph-idle: style recalcs 12 1 10.8%
subgraph-idle: layouts 0 0 100.0%
subgraph-idle: task duration 370ms 40ms 10.9%
subgraph-idle: DOM nodes 38 16 40.7%
subgraph-idle: script duration 20ms 4ms 18.6%
subgraph-idle: event listeners 11 6 52.5%
subgraph-mouse-sweep: style recalcs 80 4 4.5%
subgraph-mouse-sweep: layouts 16 0 0.0%
subgraph-mouse-sweep: task duration 758ms 159ms 21.0%
subgraph-mouse-sweep: DOM nodes 81 4 5.1%
subgraph-mouse-sweep: script duration 103ms 10ms 9.4%
subgraph-mouse-sweep: event listeners 11 0 3.6%
Raw data
{
  "timestamp": "2026-03-15T13:20:55.196Z",
  "gitSha": "46cc53faca6d8690af343ac892d5cb05924be4ea",
  "branch": "feat/oss-missing-model-error-tab",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2047.7379999999812,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 12.722999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 503.37600000000003,
      "heapDeltaBytes": 2370060,
      "domNodes": 25,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 36.202,
      "eventListeners": 30
    },
    {
      "name": "canvas-idle",
      "durationMs": 2012.5430000000506,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 15.408000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 477.567,
      "heapDeltaBytes": 1801928,
      "domNodes": 27,
      "jsHeapTotalBytes": 18612224,
      "scriptDurationMs": 33.29699999999999,
      "eventListeners": 30
    },
    {
      "name": "canvas-idle",
      "durationMs": 2033.7359999998625,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 12.768999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 415.36500000000007,
      "heapDeltaBytes": 1410252,
      "domNodes": 25,
      "jsHeapTotalBytes": 18087936,
      "scriptDurationMs": 26.454000000000004,
      "eventListeners": 30
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1945.3459999999723,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 45.666,
      "layouts": 12,
      "layoutDurationMs": 3.5320000000000005,
      "taskDurationMs": 932.298,
      "heapDeltaBytes": -2716204,
      "domNodes": 67,
      "jsHeapTotalBytes": 18874368,
      "scriptDurationMs": 143.686,
      "eventListeners": 30
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2069.3549999998595,
      "styleRecalcs": 87,
      "styleRecalcDurationMs": 52.306000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.8859999999999997,
      "taskDurationMs": 1134.686,
      "heapDeltaBytes": -3205572,
      "domNodes": 71,
      "jsHeapTotalBytes": 18612224,
      "scriptDurationMs": 153.335,
      "eventListeners": 30
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1863.0000000000564,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 45.709,
      "layouts": 12,
      "layoutDurationMs": 3.497,
      "taskDurationMs": 828.4540000000001,
      "heapDeltaBytes": -2579768,
      "domNodes": 62,
      "jsHeapTotalBytes": 19398656,
      "scriptDurationMs": 138.412,
      "eventListeners": 30
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 623.5490000001391,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 16.235,
      "layouts": 1,
      "layoutDurationMs": 0.2710000000000001,
      "taskDurationMs": 400.53299999999996,
      "heapDeltaBytes": 7525128,
      "domNodes": 71,
      "jsHeapTotalBytes": 13631488,
      "scriptDurationMs": 69.904,
      "eventListeners": 47
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 679.649000000154,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 12.373,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 446.168,
      "heapDeltaBytes": 13355436,
      "domNodes": 25,
      "jsHeapTotalBytes": 15990784,
      "scriptDurationMs": 83.88299999999998,
      "eventListeners": 26
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 598.9519999998265,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 12.177999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 381.81899999999996,
      "heapDeltaBytes": 13325160,
      "domNodes": 25,
      "jsHeapTotalBytes": 15728640,
      "scriptDurationMs": 71.49799999999999,
      "eventListeners": 26
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2028.137999999899,
      "styleRecalcs": 15,
      "styleRecalcDurationMs": 18.668,
      "layouts": 1,
      "layoutDurationMs": 0.21199999999999994,
      "taskDurationMs": 697.9000000000001,
      "heapDeltaBytes": -8616708,
      "domNodes": 74,
      "jsHeapTotalBytes": 8888320,
      "scriptDurationMs": 126.08900000000001,
      "eventListeners": 49
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2017.9829999999583,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 14.386,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 620.24,
      "heapDeltaBytes": -10109388,
      "domNodes": 26,
      "jsHeapTotalBytes": 8245248,
      "scriptDurationMs": 119.119,
      "eventListeners": 34
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2050.5820000000767,
      "styleRecalcs": 15,
      "styleRecalcDurationMs": 16.981,
      "layouts": 1,
      "layoutDurationMs": 0.196,
      "taskDurationMs": 689.256,
      "heapDeltaBytes": -8091588,
      "domNodes": 72,
      "jsHeapTotalBytes": 9699328,
      "scriptDurationMs": 129.53400000000002,
      "eventListeners": 49
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2146.4359999999942,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 18.214000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1246.143,
      "heapDeltaBytes": 2700320,
      "domNodes": 16,
      "jsHeapTotalBytes": 9236480,
      "scriptDurationMs": 448.54499999999996,
      "eventListeners": 4
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2129.9169999999776,
      "styleRecalcs": 71,
      "styleRecalcDurationMs": 20.535999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1196.4759999999999,
      "heapDeltaBytes": 2364704,
      "domNodes": 22,
      "jsHeapTotalBytes": 10280960,
      "scriptDurationMs": 448.78499999999997,
      "eventListeners": 4
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2110.6979999999567,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 18.083999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1164.089,
      "heapDeltaBytes": 4016740,
      "domNodes": 20,
      "jsHeapTotalBytes": 8445952,
      "scriptDurationMs": 427.295,
      "eventListeners": 4
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 682.1600000000672,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 16.012999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 454.48699999999997,
      "heapDeltaBytes": 12940676,
      "domNodes": 25,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 143.6,
      "eventListeners": 32
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 584.8790000000008,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 14.829000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 388.92199999999997,
      "heapDeltaBytes": 13281668,
      "domNodes": 26,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 126.006,
      "eventListeners": 36
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 606.7900000000463,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 15.270000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 406.06299999999993,
      "heapDeltaBytes": 12312308,
      "domNodes": 25,
      "jsHeapTotalBytes": 18612224,
      "scriptDurationMs": 130.56099999999998,
      "eventListeners": 32
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1996.4910000001055,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 12.469999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 473.616,
      "heapDeltaBytes": 1156156,
      "domNodes": 26,
      "jsHeapTotalBytes": 17825792,
      "scriptDurationMs": 28.715999999999998,
      "eventListeners": 32
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2010.5849999999919,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 13.456999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 445.6789999999999,
      "heapDeltaBytes": 1799048,
      "domNodes": 26,
      "jsHeapTotalBytes": 17301504,
      "scriptDurationMs": 24.711000000000002,
      "eventListeners": 30
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1994.2419999999856,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 12.750000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 435.19699999999995,
      "heapDeltaBytes": 1698992,
      "domNodes": 25,
      "jsHeapTotalBytes": 15728640,
      "scriptDurationMs": 24.332000000000008,
      "eventListeners": 30
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1990.9880000000157,
      "styleRecalcs": 86,
      "styleRecalcDurationMs": 61.53600000000001,
      "layouts": 16,
      "layoutDurationMs": 4.874,
      "taskDurationMs": 1011.151,
      "heapDeltaBytes": -6563700,
      "domNodes": 78,
      "jsHeapTotalBytes": 19398656,
      "scriptDurationMs": 115.37400000000001,
      "eventListeners": 30
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1711.9410000000244,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 42.438,
      "layouts": 17,
      "layoutDurationMs": 4.718,
      "taskDurationMs": 711.09,
      "heapDeltaBytes": 13846512,
      "domNodes": 110,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 103.249,
      "eventListeners": 49
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2028.0480000001262,
      "styleRecalcs": 85,
      "styleRecalcDurationMs": 53.888000000000005,
      "layouts": 16,
      "layoutDurationMs": 5.269,
      "taskDurationMs": 1004.8279999999999,
      "heapDeltaBytes": -6867676,
      "domNodes": 76,
      "jsHeapTotalBytes": 15990784,
      "scriptDurationMs": 112.269,
      "eventListeners": 30
    }
  ]
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
src/platform/missingModel/missingModelDownload.ts (1)

125-128: Prefer strict numeric validation for content-length.

parseInt can accept partially numeric header values. A stricter conversion plus non-negative check avoids propagating invalid file sizes.

Suggested patch
-    const parsedSize = size ? parseInt(size, 10) : null
+    const parsedSize = size === null ? null : Number(size)
     return {
-      fileSize:
-        parsedSize !== null && !Number.isNaN(parsedSize) ? parsedSize : null,
+      fileSize:
+        parsedSize !== null &&
+        Number.isFinite(parsedSize) &&
+        parsedSize >= 0
+          ? parsedSize
+          : null,
       gatedRepoUrl: null
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/missingModel/missingModelDownload.ts` around lines 125 - 128,
The code uses parseInt which accepts partial numeric strings; change the
validation for the content-length header by first testing the raw size string
with a strict numeric check (e.g., /^\d+$/) and only then converting to a
Number; set parsedSize using Number(size) or parseInt(size,10) after the regex
check and ensure you also verify Number.isInteger(parsedSize) && parsedSize >= 0
before returning fileSize (update the variables referenced: size, parsedSize and
the returned fileSize).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser_tests/tests/dialog.spec.ts`:
- Around line 98-101: The cleanup fetch inside the test setup ignores the
response; change the comfyPage.page.evaluate call (the anonymous callback passed
to comfyPage.page.evaluate and the surrounding await) to await the fetch,
inspect the response (e.g., check response.ok or response.status), and propagate
a failure if cleanup did not succeed—return a boolean or error message from the
page evaluate and then assert or throw in the test so the test fails fast when
cleanup fails (referencing comfyPage.page.evaluate and comfyPage.url to locate
the call).
- Around line 108-109: Add the missing test id key and replace hardcoded
selectors: add error-overlay: 'error-overlay' as errorOverlay under the dialogs
section of the TestIds export in browser_tests/fixtures/selectors.ts, then
update all occurrences of the string selector '[data-testid="error-overlay"]' in
dialog.spec.ts and execution.spec.ts to use TestIds.dialogs.errorOverlay (e.g.,
comfyPage.page.locator(`[data-testid="${TestIds.dialogs.errorOverlay}"]`) or
however your locator is constructed in the project).

In `@src/platform/missingModel/components/MissingModelRow.vue`:
- Around line 136-151: The Download CTA currently relies only on the
`downloadable` check, which allows the button to appear in cloud builds or when
no real destination exists; update `MissingModelRow.vue` to gate the Download
button rendering and the `handleDownload` path on `!isCloud` and a real
destination path (e.g. `folderPaths[directory] && folderPaths[directory][0]`)
instead of just `downloadable`, ensure `downloadModel()` continues to receive a
valid folder path, and adjust `downloadLabel`/`handleDownload` as needed to
reflect the destination-aware state; then extract and reuse this same predicate
in `src/components/rightSidePanel/errors/TabErrors.vue` so the Download All
button uses identical logic and remains consistent across OSS/cloud/desktop
builds.

In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 100-102: The find predicate assumes file.downloadUrl is a string
and calls .startsWith on it; change the predicate used when constructing
matchingFile in missingModelDownload.ts to first guard that file.downloadUrl is
a non-empty string (e.g., typeof file.downloadUrl === "string" &&
file.downloadUrl) before calling .startsWith(url) so malformed or missing
downloadUrl values won't throw and block metadata resolution.

In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 540-541: The showPendingWarnings() implementation only extracts
missingNodeTypes from wf.pendingWarnings before clearing it, so update
showPendingWarnings() to also extract missingModels (e.g., const {
missingNodeTypes, missingModels } = wf.pendingWarnings) and then surface them
the same way node types are handled — either by calling
surfaceMissingModels(missingModels) (or the equivalent upstream helper) or by
logging/dispatching the missingModels warnings here — before setting
wf.pendingWarnings = null; ensure you reference the existing functions
runMissingModelPipeline and surfaceMissingModels if reusing that surfacing
logic.

In `@src/scripts/app.ts`:
- Around line 1424-1433: scanAllModelCandidates is called with a
directory/category resolver only in cloud builds, leaving the third argument
undefined for OSS so scanned candidates have directory: undefined; update the
call so the same node-type→category resolver used in cloud
(useModelToNodeStore().getCategoryForNodeType) is passed in OSS as well (i.e.
replace the undefined third argument with the resolver function), keeping the
existing assetService.shouldUseAssetBrowser usage for the second argument when
isCloud is true.
- Around line 1478-1536: Create and use the same abort-controller pattern in the
OSS (else) branch as the cloud branch: call
missingModelStore.createVerificationAbortController() to get a controller,
pass/observe controller.signal in the async flows started in the else branch
(the api.getFolderPaths() promise chain and the Promise.allSettled(...) that
imports fetchModelMetadata), and before any state mutations
(missingModelStore.setFolderPaths,
useExecutionErrorStore().surfaceMissingModels, missingModelStore.setFileSize)
check controller.signal.aborted and skip updates if aborted; if
fetchModelMetadata supports a signal, pass controller.signal into it, otherwise
check controller.signal.aborted immediately after each await and before calling
setFileSize. Ensure the controller is created in the same scope as
enrichedCandidates handling so stale workflows can be aborted like in
verifyAssetSupportedCandidates.

---

Nitpick comments:
In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 125-128: The code uses parseInt which accepts partial numeric
strings; change the validation for the content-length header by first testing
the raw size string with a strict numeric check (e.g., /^\d+$/) and only then
converting to a Number; set parsedSize using Number(size) or parseInt(size,10)
after the regex check and ensure you also verify Number.isInteger(parsedSize) &&
parsedSize >= 0 before returning fileSize (update the variables referenced:
size, parsedSize and the returned fileSize).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 569c3d88-6809-4211-b8dc-bb5f9e0b86fd

📥 Commits

Reviewing files that changed from the base of the PR and between 0875e2f and 5a9db74.

📒 Files selected for processing (22)
  • browser_tests/fixtures/ComfyPage.ts
  • browser_tests/tests/dialog.spec.ts
  • browser_tests/tests/templates.spec.ts
  • src/components/dialog/content/MissingModelsContent.vue
  • src/components/dialog/content/MissingModelsFooter.vue
  • src/components/dialog/content/MissingModelsHeader.vue
  • src/components/dialog/content/missingModelsUtils.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/composables/useMissingModelsDialog.ts
  • src/locales/en/main.json
  • src/platform/missingModel/components/MissingModelCard.test.ts
  • src/platform/missingModel/components/MissingModelCard.vue
  • src/platform/missingModel/components/MissingModelRow.vue
  • src/platform/missingModel/components/MissingModelUrlInput.vue
  • src/platform/missingModel/missingModelDownload.ts
  • src/platform/missingModel/missingModelScan.ts
  • src/platform/missingModel/missingModelStore.ts
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/scripts/app.ts
💤 Files with no reviewable changes (6)
  • browser_tests/tests/templates.spec.ts
  • src/components/dialog/content/MissingModelsContent.vue
  • src/components/dialog/content/MissingModelsFooter.vue
  • src/composables/useMissingModelsDialog.ts
  • src/components/dialog/content/missingModelsUtils.test.ts
  • src/components/dialog/content/MissingModelsHeader.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/platform/missingModel/missingModelDownload.ts (1)

99-102: ⚠️ Potential issue | 🟡 Minor

The guard order is still incorrect.

The past review correctly identified this issue. The current code still calls .startsWith(url) before validating that downloadUrl exists. The logical AND short-circuits left-to-right, so file.downloadUrl.startsWith(url) executes first and will throw if downloadUrl is undefined or null.

Suggested fix
     const matchingFile = data.files?.find(
-      (file) => file.downloadUrl.startsWith(url) && file.downloadUrl
+      (file) => file.downloadUrl && file.downloadUrl.startsWith(url)
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/missingModel/missingModelDownload.ts` around lines 99 - 102, The
predicate passed to data.files?.find has the guard order reversed and can call
file.downloadUrl.startsWith when downloadUrl is null/undefined; change the
condition in the find to first check that file.downloadUrl is truthy (e.g.,
file.downloadUrl && file.downloadUrl.startsWith(url)) so startsWith is only
invoked when downloadUrl exists (this affects the matchingFile lookup used to
compute fileSize).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/platform/missingModel/missingModelDownload.ts`:
- Around line 99-102: The predicate passed to data.files?.find has the guard
order reversed and can call file.downloadUrl.startsWith when downloadUrl is
null/undefined; change the condition in the find to first check that
file.downloadUrl is truthy (e.g., file.downloadUrl &&
file.downloadUrl.startsWith(url)) so startsWith is only invoked when downloadUrl
exists (this affects the matchingFile lookup used to compute fileSize).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a21c522d-d6cf-4217-bb70-8690658a91c1

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9db74 and 7ca2ef0.

📒 Files selected for processing (1)
  • src/platform/missingModel/missingModelDownload.ts

- Assert cleanup response in E2E test beforeEach
- Guard downloadUrl type in fetchCivitaiMetadata predicate
- Pass directory resolver unconditionally for OSS model categorization
- Add abort controller to OSS branch for stale workflow protection
@jaeone94 jaeone94 force-pushed the feat/oss-missing-model-error-tab branch from 7ca2ef0 to 320a836 Compare March 14, 2026 16:57
@jaeone94 jaeone94 added the New Browser Test Expectations New browser test screenshot should be set by github action label Mar 14, 2026
@github-actions
Copy link

Updating Playwright Expectations

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Mar 14, 2026
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Mar 14, 2026
@jaeone94 jaeone94 requested a review from viva-jinyi March 14, 2026 17:16
@dante01yoon dante01yoon assigned jaeone94 and unassigned dante01yoon Mar 15, 2026
The tests covering fetchModelMetadata (HEAD/Civitai metadata fetching,
caching, request deduplication, gated repo detection) were inadvertently
deleted when the legacy missing models dialog was removed. The underlying
logic lives in missingModelDownload.ts — port the tests there.

Also add a clarifying comment to reconcileNodeErrorFlags explaining why
the ShowErrorsTab guard only affects legacy nodes.
@jaeone94 jaeone94 requested a review from dante01yoon March 15, 2026 09:46
@jaeone94 jaeone94 assigned dante01yoon and unassigned viva-jinyi and jaeone94 Mar 15, 2026
dante01yoon
dante01yoon previously approved these changes Mar 15, 2026
Copy link
Collaborator

@dante01yoon dante01yoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dante01yoon dante01yoon assigned jaeone94 and unassigned dante01yoon Mar 15, 2026
@jaeone94 jaeone94 removed the request for review from viva-jinyi March 15, 2026 12:45
@jaeone94 jaeone94 added the New Browser Test Expectations New browser test screenshot should be set by github action label Mar 15, 2026
@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Mar 15, 2026
@jaeone94 jaeone94 requested a review from dante01yoon March 15, 2026 13:25
@jaeone94 jaeone94 merged commit e2ef041 into main Mar 15, 2026
34 checks passed
@jaeone94 jaeone94 deleted the feat/oss-missing-model-error-tab branch March 15, 2026 13:46
jaeone94 added a commit that referenced this pull request Mar 15, 2026
Add a "Copy URL" button in the missing model row header for
non-asset-supported models in OSS environments. Also guard
the Download button with !isCloud to ensure neither button
appears in Cloud environments.

- Fixes #9921
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
…ialog (#9921)

## Summary
- Surface missing models in the Error Tab for OSS environments,
replacing the legacy modal dialog
- Add Download button per model and Download All button in group header
with file size display
- Move download business logic from `components/dialog/content` to
`platform/missingModel`
- Remove legacy missing models dialog components and composable

## Changes
- **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and
`surfaceMissingModels` so OSS detects missing models
- **Grouping**: Group non-asset-supported models by directory in OSS
instead of lumping into UNSUPPORTED
- **UI**: Add Download button (matching Install Node Pack design) and
Download All header button
- **Store**: Add `folderPaths`/`fileSizes` state with setter methods,
race condition guard
- **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`,
`MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils`
- **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to
Error Tab, improve test isolation
- **Snapshots**: Reset Playwright screenshot expectations since OSS
missing model error detection now causes red highlights on affected
nodes
- **Accessibility**: Add `aria-label` with model name, `aria-expanded`
on toggle, warning icon for unknown category

## Test plan
- [x] Unit tests pass (86 tests)
- [x] TypeScript typecheck passes
- [x] knip passes
- [x] Load workflow with missing models in OSS → Error Tab shows missing
models grouped by directory
- [x] Download button triggers browser download with correct URL
- [x] Download All button downloads all downloadable models
- [x] Cloud environment behavior unchanged
- [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models
in Error Tab"`

## Screenshots


https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c

 


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
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

area:nodes area:widgets enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants