- Enable saveVideo in playwright-cli config for real video recording
- Replace screenshot stitching with webm→mp4 conversion
- Move video review step before deploy so reports are included
- Add GPT video review reports inline on the Cloudflare Pages site
- Each video card now has expandable "GPT Video Review" section
- Set .playwright/cli.config.json with outputDir pointing to screenshots/
- This way bare 'playwright-cli screenshot' auto-saves to the right place
- Create screenshot directory before Claude runs (don't rely on Claude)
- Collect step now searches working directory for stray PNGs
- Simplified prompt: no --filename needed, just 'playwright-cli screenshot'
Screenshots were saved to artifact root but stitch looked in frames/.
Now: prompt tells Claude to save to screenshots/ dir with numbered names,
collect step consolidates PNGs there, stitch step globs from screenshots/.
Removed video-start/video-stop (Claude doesn't use them).
- Add playwright-cli config with outputDir and saveVideo
- Use video-start/video-stop instead of relying on screenshot frames
- Add fallback artifact collection from .playwright-cli/ default dir
- Simplify prompts to focus on video recording workflow
The escaped \$QA_ARTIFACTS in the heredoc produced literal text
'$QA_ARTIFACTS' in the prompt. Claude's Bash tool didn't reliably
expand this env var, so no screenshots or reports were saved.
Remove the escapes so the heredoc expands the variable to the actual
path (e.g. /home/runner/work/_temp/qa-artifacts).
Backtick-wrapped playwright-cli examples in the unquoted heredoc were
being interpreted as bash command substitution, producing empty prompts.
Replace backtick syntax with plain "Run:" prefixed commands.
- Remove all Xvfb/ffmpeg screen recording infrastructure from qa job
(captured blank display since playwright-cli runs headless)
- Add screenshot instructions to QA prompts: Claude saves sequential
frames to $QA_ARTIFACTS/frames/ after every interaction
- Stitch screenshots into video via ffmpeg in report job (2fps)
- Merge video-review job into report job (4 jobs → 3 jobs)
- Unified PR comment with video links + video review in <details> collapse
- Clean up stale QA_VIDEO_REVIEW_COMMENT markers from prior runs
Move extra_server_params input to env var to prevent shell injection
from untrusted input. Replace wait-for-it pip dependency with a
cross-platform curl polling loop.
Add Claude Code skills and a label-triggered QA workflow:
- .claude/skills/comfy-qa/SKILL.md: 12-category QA test plan using
playwright-cli for browser automation
- .github/workflows/pr-qa.yaml: CI workflow triggered by qa-changes
(focused, Linux) or qa-full (3-OS matrix) labels. Records screen via
ffmpeg, runs Claude CLI with playwright-cli, deploys video gallery to
Cloudflare Pages, posts PR comment with GIF thumbnails, and runs
OpenAI vision-based video review
- scripts/qa-video-review.ts: frame extraction + GPT-4o analysis
- scripts/qa-video-review.test.ts: unit tests for video review
- knip.config.ts: resolve knip errors for ingest-types package
## Summary
- Move `contain-layout contain-style` from the outer node container to
the inner wrapper to prevent CSS containment from interfering with
inter-node compositing
- Add `isolation: isolate` to the outer container for explicit stacking
context isolation
- Fixes#9988
## Root Cause
Originally, PR
[#5169](https://github.com/Comfy-Org/ComfyUI_frontend/pull/5169)
(`57db10f`, 2025-08-23) applied all three containment properties to the
outer node container:
```
contain-layout contain-style contain-paint
```
`contain-paint` ensured each node's painting was clipped to its own
boundaries, guaranteeing render isolation between sibling nodes.
Later, PR
[#6289](https://github.com/Comfy-Org/ComfyUI_frontend/pull/6289)
(`6e4471ad`, 2025-10-28) removed `contain-paint` to fix a node sizing
issue — likely because `contain-paint` clips content to the element's
padding box, which would clip selection overlays and footer tabs that
extend beyond the node boundary via negative insets.
Removing `contain-paint` while keeping `contain-layout contain-style` on
the outer container broke paint isolation between nodes. Without paint
containment, the browser's compositor no longer treated each node as an
isolated painting unit, allowing text from lower z-index nodes to
visually bleed through higher z-index nodes.
## Fix
1. **Move containment inward**: `contain-layout contain-style` is moved
from the outer container to the inner wrapper (`node-inner-wrapper`).
This preserves layout/style containment performance benefits while
keeping them scoped to the node body — away from the stacking context
boundary between sibling nodes.
2. **Explicit isolation**: `isolation: isolate` is added to the outer
container. While `transform` + `z-index` already create a stacking
context, `isolation: isolate` provides an unambiguous signal to the
browser's compositor to treat each node as an isolated compositing unit
— replacing the paint isolation role that `contain-paint` previously
served, without clipping overlays.
## Red-Green Verification
| Commit | Status | Purpose |
|--------|--------|---------|
|
[`feb8555`](https://github.com/Comfy-Org/ComfyUI_frontend/commit/feb855501)
`test:` | 🔴 Red (verified locally) | Test asserts `isolate`
on outer container and `contain-layout contain-style` on inner wrapper
only — fails against current main |
|
[`c47f89e`](https://github.com/Comfy-Org/ComfyUI_frontend/commit/c47f89ed2)
`fix:` | 🟢 Green (CI: Tests Unit passing) | Moves
containment to inner wrapper, adds `isolation: isolate` — test passes |
**Local red verification:**
```
AssertionError: expected [ 'group/node', 'lg-node', …(9) ] to include 'isolate'
❯ src/renderer/extensions/vueNodes/components/LGraphNode.test.ts:264:26
```
## Test Plan
- [x] CI red on test-only commit (verified locally — test fails without
fix)
- [x] CI green on fix commit (CI: Tests Unit passes)
- [ ] Place a String Literal node with long text on the canvas
- [ ] Place a Load Image node (with an image loaded) overlapping the
String Literal
- [ ] Verify text from the String Literal does NOT bleed through the
Load Image node
- [ ] Verify node selection outlines, error borders, and footer tabs
render correctly
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
- Read `sentry_dsn` from remote config at runtime for cloud builds,
following the same pattern as `firebase.ts` and `comfyApi.ts`
- Upload sourcemaps to both staging (`SENTRY_PROJECT`) and prod
(`SENTRY_PROJECT_PROD`) Sentry projects at build time
- Remove `SENTRY_DSN_PROD` / `USE_PROD_CONFIG`-based DSN selection — DSN
is now delivered per-environment via dynamic config
## Companion PR
- https://github.com/Comfy-Org/cloud/pull/2883 (cloud repo: dynamic
config + workflow)
## Setup required
- Add `SENTRY_PROJECT_PROD` variable (`cloud-frontend-prod`) to GitHub
STAGING environment in cloud repo
## Test plan
- [ ] Verify staging build reads staging DSN from remote config
- [ ] Verify prod build reads prod DSN from remote config
- [ ] Confirm sourcemaps are uploaded to both Sentry projects
- [ ] Validate fallback to `__SENTRY_DSN__` when remote config is
unavailable
## Summary
Fix high-resolution image preview expanding to full screen width, hiding
the close button.
## Changes
- **What**: Override scoped `ComfyImage` styles (`width/height: 100%`,
`object-fit: cover`) in the gallery context with `auto` sizing and
`max-width: 100vw; max-height: 100vh` constraints. The scoped
`[data-v-xxx]` attribute selector gave `comfy-image-main` higher
specificity than the existing `img.galleria-image` rule, so the new
selector `img.comfy-image-main.galleria-image` is used to win
specificity.
## Review Focus
- CSS specificity approach: using `.comfy-image-main.galleria-image` to
override Vue scoped styles without `!important`.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10129-fix-high-res-image-preview-overflowing-screen-and-hiding-close-button-3266d73d36508191a03bcc75f9912421)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
## Summary
- Remove the legacy missing nodes modal dialog and migrate all
functionality to the existing Error Overlay / TabErrors system
- Migrate core node version warning from `MissingCoreNodesMessage.vue`
to `MissingNodeCard` in the errors tab
- Remove `Comfy.Workflow.ShowMissingNodesWarning` setting (errors tab
always surfaces missing nodes)
- Delete 6 legacy files: `useMissingNodesDialog.ts`,
`MissingNodesContent.vue`, `MissingNodesFooter.vue`,
`MissingNodesHeader.vue`, `MissingCoreNodesMessage.vue` and its test
- Rename `showMissingNodesDialog`/`showMissingModelsDialog` params to
`showMissingNodes`/`showMissingModels`
- Add `errorOverlay` and `missingNodeCard` to centralized `TestIds`
- Migrate all E2E tests from legacy dialog selectors to error overlay
testIds
- Add new E2E test: MissingNodeCard visible via "See Errors" button flow
- Add new E2E test: subgraph missing node type verified by expanding
pack row
- Add `surfaceMissingNodes` unit tests to `executionErrorStore`
- Guard `semver.compare` against invalid version strings
- Add `role="alert"`, `aria-hidden` for accessibility
- Use reactive props destructuring in `MissingNodeCard` and
`MissingPackGroupRow`
**Net change: -669 lines** (19 files, +323 / -992)
<img width="733" height="579" alt="image"
src="https://github.com/user-attachments/assets/c497809d-b176-43bf-9872-34bd74c6ea0d"
/>
## Test plan
- [x] Unit tests: MissingNodeCard core node warning (7 tests)
- [x] Unit tests: surfaceMissingNodes (4 tests)
- [x] Unit tests: workflowService showPendingWarnings (updated)
- [x] E2E: Error overlay visible on missing nodes workflow
- [x] E2E: Error overlay visible on subgraph missing nodes
- [x] E2E: MissingNodeCard visible via See Errors button
- [x] E2E: Subgraph node type visible after expanding pack row
- [x] E2E: Error overlay does not resurface on undo/redo
- [x] E2E: Error overlay does not reappear on workflow tab switch
- [x] Typecheck, lint, knip all passing
## Related issues
- Closes#9923 (partially — `errorOverlay` and `missingNodeCard` added
to TestIds)
- References #10027 (mock hoisting inconsistency)
- References #10033 (i18n-based test selectors)
- References #10085 (DDD layer violation + focusedErrorNodeId)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10102-refactor-remove-legacy-missing-nodes-dialog-3256d73d365081c194d2e90bc6401846)
by [Unito](https://www.unito.io)
Resolve all lint warnings (3 oxlint + 1 eslint).
## Changes
- Replace `it.todo` with `it.skip` in subgraph tests (`warn-todo`)
- Move `vi.mock` to top-level in `firebaseAuthStore.test.ts`
(`hoisted-apis-on-top`)
- Rename `DOMPurify` default import to `dompurify`
(`no-named-as-default`)
---
### The Villager Who Ignored the Warnings
Once there lived a villager whose compiler whispered of lint. "They are
only *warnings*," she said, and went about her day. One warning became
three. Three became thirty. The yellow text grew like ivy across the
terminal, until no one could tell the warnings from the errors. One
morning a real error appeared — a misplaced mock, a shadowed import —
but nobody noticed, for the village had long since learned to stop
reading. The build shipped. The users wept. And the warning, faithful to
the last, sat quietly in the log where it had always been.
*Moral: Today's warning is tomorrow's incident report.*
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9972-fix-resolve-all-lint-warnings-3246d73d3650810a89cde5d05e79d948)
by [Unito](https://www.unito.io)
Co-authored-by: Amp <amp@ampcode.com>
## Summary
Restrict PyPI publishing of `comfyui-frontend-package` to bi-weekly
ComfyUI release cycles only, instead of every nightly version bump.
## Changes
- **What**: Move `publish_pypi` job from `release-draft-create.yaml` to
`release-biweekly-comfyui.yaml`
1. Removed `publish_pypi` job from `release-draft-create.yaml` (no
longer publishes on every merged Release PR)
2. Added `publish-pypi` job to `release-biweekly-comfyui.yaml` with tag
polling, build, publish, and PyPI availability confirmation
3. Gated `create-comfyui-pr` on `publish-pypi` success so the ComfyUI
requirements bump PR is only created after the package is confirmed
available
4. Updated ComfyUI PR body to confirm PyPI availability instead of
warning about a pending release PR
- **Breaking**: None — nightly releases still create GitHub releases and
publish npm types; only PyPI publishing timing changes
- **Dependencies**: None
## Review Focus
- The `publish-pypi` job uses `if: always() &&
needs.resolve-version.result == 'success'` to run even when
`trigger-release-if-needed` is skipped (tag already exists)
- Tag polling (30min timeout) waits for the version bump PR to be merged
before building from the tagged commit
- PyPI propagation polling (15min timeout) confirms the package is
installable before creating the ComfyUI PR
Fixes COM-16778
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9948-ops-restrict-PyPI-publishing-to-bi-weekly-ComfyUI-releases-3246d73d36508198b00fcc247ac5b58c)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
- Adds `gh pr merge --auto --squash` after backport PR creation in the
backport workflow, so backport PRs merge automatically once checks pass
- Uses `|| echo "::warning::..."` fallback to avoid failing the workflow
if auto-merge can't be enabled (e.g. repo setting not configured)
## Test plan
- [ ] Trigger backport workflow on a test PR with `needs-backport` label
- [ ] Verify auto-merge is enabled on the created backport PR
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10108-chore-enable-auto-merge-on-backport-PRs-3256d73d3650814eb6e5fb2bdf3c5ec7)
by [Unito](https://www.unito.io)
## Summary
Restore the existing job details popover in the expanded queue overlay
after the list row UI moved to `AssetsListItem`.
## Changes
- **What**: Add delayed hover-driven job details popover support back to
`JobAssetsList`, including teleported positioning, row-to-popover hover
handoff, and focused component coverage for the timing behavior.
## Review Focus
Please verify the hover transition between the queue row and the
teleported popover, especially around positioning and whether the
popover remains reachable in the top menu queue overlay.
## Screenshots (if applicable)
N/A
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9549-fix-restore-queue-job-details-popover-31d6d73d3650815eaf45dcc2a14d3dee)
by [Unito](https://www.unito.io)
## Summary
Remove team assignments from CODEOWNERS to reduce notification noise for
internal PRs. Add a workflow that requests team review only when
external contributors open PRs.
## Changes
- **What**: Strip `@Comfy-org/comfy_frontend_devs` and
`@Comfy-Org/comfy_maintainer` from all CODEOWNERS entries (keep
individual user assignments). Add `pr-request-team-review.yaml` workflow
that uses `pull_request_target` to request team review for
non-collaborator PRs.
- **Dependencies**: None
## Review Focus
- The workflow uses `pull_request_target` but does not check out or
execute any untrusted code — it only runs `gh pr edit --add-reviewer`.
- The `author_association` check excludes OWNER, MEMBER, and
COLLABORATOR — internal PRs will not trigger team review requests.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10104-chore-replace-team-CODEOWNERS-with-external-PR-review-workflow-3256d73d3650813b887ac16b5e97b4c4)
by [Unito](https://www.unito.io)
## Summary
- Adds a **Paste Image** context menu option to nodes that support image
pasting (e.g. Load Image), complementing the existing **Copy Image**
option
- Reads clipboard image data via `navigator.clipboard.read()` and
delegates to `node.pasteFiles()` — same path as `Ctrl+V` paste
- Only shown on nodes that implement `pasteFiles` (e.g. LoadImage)
- Available even when no image is loaded yet (e.g. fresh LoadImage node)
- **Node 2.0 context menu only** — legacy litegraph menu is not
supported
- Fixes#9989
<img width="852" height="685" alt="스크린샷 2026-03-16 오후 5 34 28"
src="https://github.com/user-attachments/assets/219e8162-312a-400b-90ec-961b95b5f472"
/>
## Test plan
- [x] Right-click a Load Image node (with or without image loaded) →
verify "Paste Image" appears
- [x] Copy an image to clipboard → click "Paste Image" → verify image is
loaded into the node
- [x] Verify "Paste Image" does not appear on output-only image nodes
(e.g. Save Image, Preview Image)
- [x] Unit tests pass: `pnpm vitest run
src/composables/graph/useImageMenuOptions.test.ts`
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Documentation Audit - File Path Updates
## Summary
Conducted a comprehensive audit of all documentation files across the
repository to ensure 100% accuracy of file paths, code references, and
technical information. Fixed outdated file path references that resulted
from codebase restructuring.
## Changes Made
### File Path Corrections
**docs/SETTINGS.md**
- Updated `src/constants/coreSettings.ts` →
`src/platform/settings/constants/coreSettings.ts` (3 occurrences)
- Updated `src/stores/settingStore.ts` →
`src/platform/settings/settingStore.ts` (2 occurrences)
- Updated `src/services/newUserService.ts` →
`src/services/useNewUserService.ts` (1 occurrence)
**src/locales/CONTRIBUTING.md**
- Updated `src/constants/coreSettings.ts` →
`src/platform/settings/constants/coreSettings.ts` (1 occurrence)
**docs/testing/unit-testing.md**
- Updated `@/domains/workflow/validation/schemas/workflowSchema` →
`@/platform/workflow/validation/schemas/workflowSchema` (1 occurrence)
## Audit Results
### Files Audited (✅ All Pass)
- ✅ Core documentation: README.md, CONTRIBUTING.md, CLAUDE.md, AGENTS.md
- ✅ Docs directory: 27 files in docs/**/*.md
- ✅ Claude commands: 7 files in .claude/commands/*.md
- ✅ Locales documentation: src/locales/CONTRIBUTING.md
- ✅ Package.json scripts: All documented commands verified against
actual scripts
### Accuracy Statistics
- **Total file path references audited:** 50+
- **Broken references found:** 5
- **Broken references fixed:** 5
- **Documentation files scanned:** 31
- **Final accuracy rate:** 100%
### Verified as Accurate
- ✅ All package.json script references in AGENTS.md match actual scripts
- ✅ All configuration file references (.vscode/extensions.json,
vite.config.mts, etc.) point to existing files
- ✅ All code examples reference actual existing files
- ✅ All ADR (Architecture Decision Records) dates and references are
correct
- ✅ Extension API examples match current implementation
- ✅ Feature flag system documentation aligns with actual code
- ✅ Settings system documentation reflects current architecture
- ✅ Testing guides reference correct test file locations
- ✅ Vue component conventions match actual patterns
- ✅ TypeScript conventions align with codebase standards
## Review Notes
This audit was triggered by a codebase restructuring where
settings-related files were moved from `src/constants/` and
`src/stores/` to a new platform-based organization under
`src/platform/settings/`. Additionally, some service files were renamed
to follow the `use*` composable naming convention.
All documentation now accurately reflects the current codebase
structure. No functionality changes were made - only documentation
updates to maintain accuracy.
### Files with Intentionally Deleted References (No Fix Needed)
- `build/plugins/generateImportMapPlugin.ts` - Documented as removed in
ADR-0005, reference is intentionally showing historical context
## Testing
- Verified all updated file paths exist in the codebase
- Confirmed all code examples still compile with updated import paths
- Validated no broken internal documentation links
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10029-docs-Weekly-Documentation-Update-3256d73d36508177b363d4160085bbe9)
by [Unito](https://www.unito.io)
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
## 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>
## 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)
## 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)
## 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)
## 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)
## 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>
## 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)