Backport of #10123, #9967, and #9972 to `core/1.42`
Includes three cherry-picks in dependency order:
1. #9972 — `fix: resolve all lint warnings` (clean)
2. #9967 — `test: harden subgraph test coverage and remove low-value
tests` (clean)
3. #10123 — `test: subgraph integration contracts and expanded
Playwright coverage` (1 conflict, auto-resolved by rerere from #10326)
See #10326 for core/1.41 backport with detailed conflict resolution
notes.
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
Backport of #10314 to `core/1.42`
Automatically created by backport workflow.
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
## 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)
## 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>
## 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.
## 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>
## 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.
## 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>
## 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)
## Summary
Fix /cloud/subscribe route hanging indefinitely because billing context
never initializes during the onboarding flow.
## Changes
- **What**: Replace passive `await until(isInitialized).toBe(true)` with
explicit `await initialize()` in CloudSubscriptionRedirectView. Remove
unused `until` import.

## Review Focus
In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not
set, so `useBillingContext`'s internal watch (which triggers
`initialize()` on workspace change) enters the `!newWorkspaceId` branch
— it resets `isInitialized` to `false` and returns without ever calling
`initialize()`. The old code then awaited `isInitialized` becoming
`true` forever.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a)
by [Unito](https://www.unito.io)
Co-authored-by: Amp <amp@ampcode.com>
## 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>
## 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>
## 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)