Thumbnail downscaling is currently being used in more places than it
should be.
- Nodes which display images will display incorrect resolution
indicators
<img width="255" height="372" alt="image"
src="https://github.com/user-attachments/assets/674790b6-04c8-4db0-84c2-2fa2dbaf123d"
/> <img width="255" height="372" alt="image"
src="https://github.com/user-attachments/assets/1dbe751b-7462-4408-9236-9446b005f5fc"
/>
This is particularly confusing with output nodes, which claim the output
is not of the intended resolution
- The "Download Image" and "Open Image" context menu actions will
incorrectly download the downscaled thumbnail.
- The assets panel will incorrectly display the thumbnail resolution as
the resolution of the output
- The lightbox (zoom) of an image will incorrectly display a downscaled
thumbnail.
This PR is a quick workaround to staunch the major problems
- Nodes always display full previews.
- Resolution downscaling is applied on the assert card, not on the
assetItem itself
- Due to implementation, this means that asset cards will still
incorrectly show the resolution of the thumbnail instead of the size of
the full image.
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Cloud build dispatch was only triggering on the `labeled` event, not on
subsequent pushes to PRs that already had a preview label.
## Changes
- **What**: Add `synchronize` to `pull_request` event types and update
the `if` condition to support all three preview labels (`preview`,
`preview-cpu`, `preview-gpu`). For `labeled` events, check the added
label name; for `synchronize` events, check existing PR labels.
## Review Focus
The `if` condition now branches on `github.event.action` to use the
correct label-checking mechanism for each event type.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9636-fix-dispatch-cloud-build-on-synchronize-for-preview-labeled-PRs-31e6d73d3650814e9069e37d6199ffc9)
by [Unito](https://www.unito.io)
As a temporary fix for widgets being incorrectly hidden, #9669 allowed
all disabled widgets to be displayed.
This PR provides a more robust implementation to derive whether the
widget, as would be displayed from the root graph, is disabled.
Potential regression:
- Drag drop handlers are applied on node, not widgets. A subgraph
containing a "Load Image" node, does not allow dragging and dropping an
image onto the subgraphNode in order to load it. Because app mode
widgets would display from the original owning node prior to this PR,
these drag/drop handlers would apply. Placing "Load Image" nodes. I
believe this change makes behavior more consistent, but it warrants
consideration.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9671-Restore-hiding-of-linked-inputs-in-app-mode-31e6d73d365081688e37fbb931f3af68)
by [Unito](https://www.unito.io)
## Summary
Adds `isGraphReady` getter to `ComfyApp` and uses it in
`executionErrorStore` guards to prevent false 'ComfyApp graph accessed
before initialization' error logs during early store evaluation.
## Changes
- **What**: Added `isGraphReady` boolean getter to `ComfyApp` that
safely checks graph initialization without triggering the `rootGraph`
getter's error log. Updated 5 guard sites in `executionErrorStore` to
use `app.isGraphReady` instead of `app.rootGraph`.
- **Why**: The `rootGraph` getter logs an error when accessed before
initialization. Computed properties and watch callbacks in
`executionErrorStore` are evaluated early (before graph init), causing
false error noise in the console.
## Review Focus
- `isGraphReady` is intentionally minimal — just
`!!this.rootGraphInternal` — to avoid duplicating the error-logging
behavior of `rootGraph`
- The `watch(lastNodeErrors, ...)` callback now checks `isGraphReady` at
the top and early-returns, consistent with the computed property pattern
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9672-fix-add-isGraphReady-guard-to-prevent-premature-graph-access-error-logs-31e6d73d365081be8e1fc77114ce9382)
by [Unito](https://www.unito.io)
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Adds a concise guide to `docs/release-process.md` explaining how the
release workflows interact, with focus on the version semantics that
differ between minor and patch bumps.
Key sections:
- How minor bumps freeze the previous minor into `core/` and `cloud/`
branches
- How patch bumps on `main` vs `core/X.Y` differ (published vs draft
releases)
- Why unreleased commits are dual-homed when a minor bump happens
- Summary table, backporting, publishing, and bi-weekly automation
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9548-docs-add-release-process-guide-31d6d73d365081f2bdaace48a7cb81ae)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
App mode stores the state of selected widgets as a tuple of `[NodeId,
WidgetName]`. With recent subgraph changes, for a given node,
`widget.name` will no longer uniquely resolve to a single widget.
- From both Vue and Litegraph, selecting an input for display in App
mode will now resolve the NodeId of the node which owns the widget
instead of the selected node.
- When displaying selections in litegraph, if the NodeId does not exist
in the current graph, instead of resolving the actual node the rootGraph
is searched for any subgraphNode which contains a view matching the
`[NodeId, WidgetName]` pair.
- When displaying widgets in App mode, the widget is always set as being
a view of the real widget (This means that they will not display a
purple promotion border.
Known Issue:
- These same subgraph changes made it so that a widget can be linked
without being disabled. This PR makes it so widgets which have been
linked instead display normally under the assumption that they are
incorrectly marked as disabled. As disabled widgets can not be selected
as inputs, this should handle normal usage fine, but a better solution
is being investigated
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9669-Always-use-interior-nodeId-for-app-mode-31e6d73d365081f8a918d0e43cb659ee)
by [Unito](https://www.unito.io)
## Summary
When the actionbar is floating and has no docked buttons, the container
is now hidden (zero-width, transparent border) to avoid showing an empty
rounded box. Additionally, the error/destructive border is now applied
to the floating actionbar panel itself (via `ComfyActionbar`) instead of
the container, so it appears in the correct location when floating.
## Changes
- **TopMenuSection**: Added `hasDockedButtons` and
`isActionbarContainerEmpty` computed properties to detect when the
docked container has no visible buttons; `actionbarContainerClass`
computed hides the container by collapsing it when empty and floating,
while preserving the legacy drop zone via `:has(.border-dashed)` CSS
selector
- **TopMenuSection**: Error border
(`border-destructive-background-hover`) is now only applied to the
docked container when the actionbar is **not** floating
- **ComfyActionbar**: Accepts new `hasAnyError` prop and applies the
error border to the floating panel's `panelClass` when floating
## Review Focus
- The `has-[.border-dashed]` CSS selector restores the container visuals
when a legacy drag-target element is present inside it — verify this
works as expected
- Error border placement: docked mode shows border on container,
floating mode shows border on the fixed panel
## Screenshots
https://github.com/user-attachments/assets/75caabac-e391-4bfd-b4dc-62d564e55d37
## Problem
Output assets in the assets panel show content hashes (e.g.,
`a1b2c3d4.png`) instead of display names (e.g., `ComfyUI_00001_.png`).
## Root Cause
Cloud inference replaces `filename` with the content hash in the output
transform pipeline. The hashed filename gets stored in the jobs table's
`preview_output` JSONB. The frontend uses this hash as the display name.
## Solution
- Add `display_name` field to `AssetItem` schema and `ResultItemImpl`
- Backend (cloud PR) joins job→assets table to resolve the original name
and injects `display_name` into job responses
- Frontend prefers `display_name` over `name` **only for display text
and download filenames**
- `asset.name` remains unchanged (the hash) for URLs, drag-to-canvas,
export filters, and output key dedup
## Backwards Compatible
- OSS: `display_name` is undefined, falls back to `asset.name` (which is
already the real filename in OSS)
- Cloud pre-deploy: `display_name` absent from API, falls back
gracefully
- Old jobs with no assets: `display_name` not injected, no change
## Cloud PR
https://github.com/Comfy-Org/cloud/pull/2747https://github.com/user-attachments/assets/8a4c9cac-4ade-4ea2-9a70-9af240a56602
## Summary
After a user runs the workflow once in graph mode, switching to app mode
with no app built, incorrectly showed the app mode outputs view instead
of the intro screen
## Changes
- **What**: don't try and select outputs if no outputs & filter out all
outputs when nothing chosen
## Summary
Fix regression from PR #9535 where the multi-output count badge stopped
appearing in the asset sidebar.
## Root Cause
PR #9535 changed `outputCount` in `mapHistoryToAssets` from
`job.outputs_count` (backend-provided total) to
`task.previewableOutputs.length`. However, `TaskItemImpl` constructed
from a job listing only has the single `preview_output`, so
`previewableOutputs.length` is always **1** — the multi-output badge
never appears.
## Fix
Use the backend-provided `outputs_count` (via `task.outputsCount`) with
fallback to `task.previewableOutputs.length` when unavailable. This
restores the correct count while preserving the fallback for jobs that
don't have `outputs_count` from the server.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9627-fix-restore-backend-outputs_count-for-asset-sidebar-multi-output-badge-31d6d73d36508160b93fd03af4a01aa3)
by [Unito](https://www.unito.io)
## Summary
fix#8097.
This PR shifts the Mask Editor Brush Size slider from a linear scale to
a logarithmic (exponential) scale. Previously, the linear 1-250 range
heavily clumped the usable, small "fine-detail" brush sizes (e.g., 1px
to 20px) into the very first 10% of the slider, making it extremely
difficult to select precise sizes with the mouse.
This update borrows UX paradigms from other standard image editors like
Photoshop and GIMP, which map their scale entry widgets on an
exponential curve.
## GIMP Source
By inspecting the official **GIMP** source code under
`libgimpwidgets/gimpscaleentry.c`, we can see this exact mathematical
relationship being utilized when the logarithmic property is marked TRUE
on a brush radius adjustment widget:
```
// Mapping visual slider to internal value
value = gtk_adjustment_get_lower(...) + exp(t);
// Mapping internal value to visual slider
t = log (value - gtk_adjustment_get_lower(...) + 0.1);
```
https://github.com/user-attachments/assets/6d59ff12-f623-42cc-a52b-84147e9bb90b
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9534-fix-maskeditor-make-brush-size-slider-logarithmic-8097-31c6d73d365081118508e8363e0c5312)
by [Unito](https://www.unito.io)
## Summary
Fix asset sidebar badge showing inflated output count by using
previewable outputs length instead of raw server count.
## Changes
- **What**: Changed `outputCount` in `mapHistoryToAssets` from
`job.outputs_count` (includes all output types: text, JSON, custom data)
to `task.previewableOutputs.length` (only image, video, audio, 3D). The
badge now matches what users actually see in the expanded view.
## Review Focus
One-line change. The `task.previewableOutputs` array is already computed
on the line immediately below and used for `allOutputs`, so this
introduces no new computation.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9535-fix-use-previewable-output-count-for-asset-sidebar-badge-31c6d73d365081c49161caec64cf3921)
by [Unito](https://www.unito.io)
## Summary
Fix non-widget inputs on nested subgraphs appearing twice — once as
slots and once as unresolved button widgets.
## Changes
- **What**: Add `getTargetWidget()` guard in the `isSubgraphNode()`
branch of `resolveSubgraphInputTarget`, matching the existing check for
non-subgraph nodes. Non-widget inputs (e.g. AUDIO, IMAGE) now return
`undefined` instead of a bogus promotion entry.
## Review Focus
`resolveSubgraphInputTarget` had an asymmetry: the non-subgraph branch
checked `getTargetWidget()` before returning, but the `isSubgraphNode()`
branch returned unconditionally for every input. For nested subgraphs
where non-widget slots are linked through to inner SubgraphNode inputs,
this created `PromotedWidgetView` entries that failed `resolveDeepest()`
(falling back to `type: 'button'`), while the inputs also rendered as
normal slot circles since `input.widget` was never set by
`_resolveInputWidget` (which correctly skipped them).
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9542-fix-prevent-non-widget-inputs-on-nested-subgraphs-from-appearing-as-button-widgets-31c6d73d3650816387c3f97f0385e762)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
## Summary
Remove the workspace switching confirmation dialog since switching
workspaces no longer discards unsaved changes.
## Changes
- **What**: Remove `hasUnsavedChanges` check, `dialogService.confirm`
call, and unused imports (`useI18n`, `useWorkflowStore`,
`useDialogService`) from `useWorkspaceSwitch`. Rename
`switchWithConfirmation` to `switchWorkspace`. Update callers
(`WorkspaceSwitcherPopover.vue`, `InviteAcceptedToast.vue`). Remove
`workspace.unsavedChanges` i18n entries from all 12 locale files.
Simplify tests to cover core switching behavior only.
## Review Focus
The confirmation dialog was showing inaccurate information (warning
about discarding unsaved changes when that no longer happens). This is a
pure removal with no new behavior.
<!-- Pipeline-Ticket: COM-15441 -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9250-fix-remove-workspace-switching-confirmation-dialog-3136d73d365081d3b959da22e8f151d1)
by [Unito](https://www.unito.io)
## Summary
During workflow loading, the workflow data & active workflow object can
be out of sync, meaning any checkState calls will overwrite data into
the wrong workflow.
Recreation steps:
* Open 2-3 workflows
* Enter builder mode > select step
* Select some different inputs on each
* Quickly tap the shift key (this triggers checkState) while switching
tabs
* After a while, you'll see the wrong inputs on the workflows
Alternatively, register an extension that guarantees to call checkState
during the bad phase, run this in browser devtools and switch tabs:
```
window.app.registerExtension({
name: 'bad',
async afterConfigureGraph() {
window.app.extensionManager.workflow.activeWorkflow.changeTracker.checkState()
}
})
```
## Changes
- **What**:
- Add loading graph flag
- Prevent checkState calls while loading
- Prevent app mode data sync while loading
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9531-fix-Prevent-corruption-of-workflow-data-due-to-checkState-during-graph-loading-31c6d73d365081e2ab91d9145bf1d025)
by [Unito](https://www.unito.io)
## Summary
Standardize the repo's Node contract on 24 while centralizing workflow
resolution through `.nvmrc` so local setup, CI, and package metadata
stay aligned from one version file.
## Changes
- **What**: Add `package.json` `engines.node = 24.x`, switch every
`actions/setup-node` workflow in the repo to `node-version-file:
'.nvmrc'`, and update contributor and Playwright docs to point to
`.nvmrc` as the Node source of truth.
## Review Focus
The workflow behavior should be unchanged apart from sourcing the Node
version from `.nvmrc` instead of repeating literals like `20`, `22`,
`24.x`, or `lts/*`. GitHub's formatter also moved the new `engines`
block to the package metadata section near the end of `package.json`.
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Dispatch a `frontend-asset-build` event to the cloud repo when the
`preview` label is added to a PR, so cloud can build preview assets.
## Changes
- **What**: Extended `cloud-dispatch-build.yaml` to trigger on
`pull_request` `labeled` events filtered to the `preview` label. The
payload sends the PR head SHA and branch.
## Review Focus
- The `pull_request` trigger gives a read-only `GITHUB_TOKEN`, but the
dispatch step uses `CLOUD_DISPATCH_TOKEN` so this is fine.
- Fork PRs are blocked by the existing `github.repository` guard.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9518-feat-dispatch-cloud-build-when-preview-label-is-added-to-PR-31c6d73d365081a8aab6f585960977f6)
by [Unito](https://www.unito.io)
## Summary
Additional fixes and updates based on testing
## Changes
- **What**:
- add warning to welcome screen & when sharing an app that has had all
outputs removed
- fix target workflow when changing mode via tab right click menu
- change build app text to be conditional "edit" vs "build" depending on
if an app is already defined
- update empty apps sidebar tab button text to make it clearer
- remove templates button from app mode (we will reintroduce this once
we have app templates)
- add "exit to graph" after applying default mode of node graph
- update cancel button to remove item from queue if it hasn't started
yet
- improve scoping of jobs/outputs to the current workflow [not perfect
but should be much improved]
- close sidebar tabs on entering app mode
- change tooltip to be under the workflow menu rather than covering the
button
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9511-feat-fix-App-mode-QA-feedback-2-31b6d73d365081d59bbbc13111100d46)
by [Unito](https://www.unito.io)
## Summary
Refactors the error and subgraph node footer UI by extracting a
dedicated `NodeFooter` component and replacing the CSS `outline`
approach with a layered border overlay for selection/executing state
indicators.
## Changes
- **What**: Extracted `NodeFooter.vue` from `LGraphNode.vue` to
encapsulate the footer tab logic (subgraph enter, error, advanced
inputs). Replaced CSS `outline` with an absolutely-positioned border
overlay div for selection and executing state. Added a separate root
border overlay div for the node body border. Removed unused
`isTransparent` function from `colorUtil.ts`.
- **Dependencies**: None
## Review Focus
- The layered overlay approach (`absolute -inset-[3px] border-3`) for
selection/executing outlines vs the previous `outline-3` approach —
ensures the outline renders outside the node bounds correctly including
the footer area
- `NodeFooter` handles 4 cases: subgraph+error (dual tabs), error only,
subgraph only, advanced inputs — verify edge cases render correctly
- Resize handle bottom offset adjustments for nodes with footers
(`hasFooter`)
## Screenshots
<img width="1142" height="603" alt="image"
src="https://github.com/user-attachments/assets/e0d401f0-8516-4f5f-ab77-48a79530f4bd"
/>
<img width="1175" height="577" alt="image"
src="https://github.com/user-attachments/assets/bcf08fff-728a-491c-add9-5b96d2f3bfce"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9360-style-Update-error-subgraph-node-footer-design-with-layered-overlay-approach-3186d73d365081b2ac31f166f4d1944a)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
Disable missing model warnings in browser tests by default.
Browser tests run without model files on disk, so workflows that embed
model metadata can render differently in CI than the test actually
intends to cover. The viewport screenshot golden had started depending
on the missing-model popup even though the test is only about restoring
an offscreen viewport.
Set `Comfy.Workflow.ShowMissingModelsWarning` to `false` in the shared
Playwright fixture, keep the missing-model dialog coverage by explicitly
enabling the setting in the dialog tests, and update the viewport
screenshot expectation to the no-popup rendering.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9513-test-disable-missing-model-warnings-in-browser-tests-31b6d73d365081d1908bfe11ec0c3bc2)
by [Unito](https://www.unito.io)
## Summary
Extract the inner link-resolution loop from
`_internalConfigureAfterSlots` into a private `_resolveInputWidget`
method to reduce cognitive complexity below the sonarjs threshold of 15.
## Changes
- **What**: Extract nested loop body (lines 654-689) into
`_resolveInputWidget` private method in `SubgraphNode.ts`
- Pure refactoring with no behavioral changes
## Review Focus
Straightforward extract-method refactoring. The new method contains the
exact same logic that was previously inline.
Fixes#9297
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9383-refactor-extract-input-widget-resolution-from-SubgraphNode-configure-3196d73d365081ba9124cfd0d312fcb0)
by [Unito](https://www.unito.io)
## Summary
Extract and harden subgraph node ID deduplication to prevent widget
store key collisions when multiple subgraph copies share identical node
IDs.
## Changes
- **What**: Extract `deduplicateSubgraphNodeIds` from `LGraph.ts` into
`utils/subgraphDeduplication.ts`, decomposed into focused helpers
(`remapNodeIds`, `findNextAvailableId`, `patchSerialisedLinks`,
`patchPromotedWidgets`, `patchProxyWidgets`). Clone inputs internally so
caller data is never mutated. Add safety limit on ID search to prevent
unbounded loops. Add `console.warn` on remapped IDs matching existing
`ensureGlobalIdUniqueness` behavior. Add test fixture and 5 behavioral
tests covering ID remapping, link patching, promoted widget patching,
proxyWidget patching, and no-op when IDs are unique.
## Review Focus
- The cloning strategy in `deduplicateSubgraphNodeIds` — it
`structuredClone`s subgraphs and rootNodes, returning the clones. The
caller uses `effectiveNodesData` to thread the patched root nodes
through to node creation.
- The `MAX_NODE_ID` safety limit (100M) — is this a reasonable ceiling?
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9510-fix-extract-and-harden-subgraph-node-ID-deduplication-31b6d73d365081f48c7de75e2bfc48b3)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
- Increased the z-index on app mode outputs so that they display above a
zoomed image
- The "view job" button on the job queued toast in mobile app mode will
take you to outputs instead of assets
- Image previews now have a minimum zoom of ~20% and a maximum zoom of
~50x
- The enter panel in linear mode now has a minimum size of ~1/5th screen
size
- In arrange mode, dragging to rearrange inputs will no longer cause a
horizontal scrollbar to appear.
- Videos will now display the first frame instead of a generic video
icon
- Muted/Bypassed nodes can no longer be selected as inputs/outputs, or
be displayed when in app mode.
- Linked input can no longer be selected or displayed
- Adds a share workflow button in app mode and wires up the existing
context menu
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9432-More-app-fixes-31a6d73d365081509cd0ea74bfdc9b95)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Fixes flakey test to only assert that the upload request is made with
the correct URL
## Changes
- **What**
- Replace waitForResponse with waitForRequest for the no_workflow.webp
upload test to only assert the request is initiated with the correct URL
- Move request listener setup before the drag-drop action to avoid race
conditions
- Remove screenshot assertion for the upload case since the upload may
not complete before the screenshot is taken
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9488-fix-update-loadWorkflowInMedia-test-to-only-assert-upload-request-URL-31b6d73d365081f69a9aeb1095da7d60)
by [Unito](https://www.unito.io)
## Summary
Migrate hardcoded litegraph canvas keybindings (Ctrl+A/C/V, Delete,
Backspace) into the customizable keybinding system so users can remap
them via Settings > Keybindings.
## Changes
- **What**: Register Ctrl+A (SelectAll), Ctrl+C (CopySelected), Ctrl+V
(PasteFromClipboard), Ctrl+Shift+V (PasteFromClipboardWithConnect),
Delete/Backspace (DeleteSelectedItems) as core keybindings in
`defaults.ts`. Add new `PasteFromClipboardWithConnect` command. Remove
hardcoded handling from litegraph `processKey()`, the `app.ts` Ctrl+C/V
monkey-patch, and the `keybindingService` canvas forwarding logic.
Fixes#1082Fixes#2015
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9459-feat-expose-litegraph-internal-keybindings-31b6d73d3650819a8499fd96c8a6678f)
by [Unito](https://www.unito.io)
## Summary
Standardize 5 English pluralization strings from incorrect 3-part format
to proper 2-part `"singular | plural"` format.
## Changes
- **What**: Convert `nodesCount`, `asset`, `errorCount`,
`downloadsFailed`, and `exportFailed` i18n keys from redundant 3-part
pluralization (zero/one/many) to standard 2-part English format
(singular/plural)
## Review Focus
The 3-part format (`a | b | a`) was redundant for English since the
first and third parts were identical. vue-i18n only needs 2 parts for
English pluralization.
Fixes#9277
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9384-fix-standardize-i18n-pluralization-to-two-part-English-format-3196d73d365081cf97c4e7cfa310ce8e)
by [Unito](https://www.unito.io)
## Summary
Add console.warn deprecation notice when the legacy ComfyList
queue/history menu is instantiated.
## Changes
- **What**: Log a deprecation warning in the `ComfyList` constructor
telling users the legacy menu is deprecated, may break, and won't
receive support. Includes instructions to switch via Settings → "Use new
menu" → "Top".
## Review Focus
Wording of the user-facing console warning.
Fixes#8100 (Phase 1)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9460-chore-add-deprecation-warning-for-legacy-queue-history-menu-31b6d73d365081ffa041cad33e8cd9a7)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Add SVG-based brand loading indicators (LogoCFillLoader,
LogoComfyWaveLoader) and use the wave loader as the app loading screen.
## Changes
- **What**: New `LogoCFillLoader` (bottom-to-top fill, plays once) and
`LogoComfyWaveLoader` (wave water-fill animation) components with
`size`, `color`, `bordered`, and `disableAnimation` props. Move all
loaders from `components/common/` to `components/loader/`. Use
`LogoComfyWaveLoader` in `App.vue` and `WorkspaceAuthGate.vue`. Render
loader above BlockUI overlay (z-1200) to prevent dim wash-out.
- **Dependencies**: None
## Review Focus
- SVG mask-based animation approach using `currentColor` for flexible
theming
- z-index layering: loader at z-1200 renders above PrimeVue BlockUI's
z-1100 modal overlay
- `disableAnimation` prop used in WorkspaceAuthGate to show static logo
outline during auth loading
## Screenshots (if applicable)
[loading_record.webm](https://github.com/user-attachments/assets/b34f7296-9904-4a42-9273-a7d5fda49d15)
Storybook stories added for both components under `Components/Loader/`.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9433-feat-add-Logo-C-fill-and-Comfy-wave-loading-indicator-components-31a6d73d3650811cacfdcf867b1f835f)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Fix flaky `no_workflow.webp` screenshot test by waiting for async upload
and `/view` response before asserting.
## Changes
- **What**: In `loadWorkflowInMedia.spec.ts`, added `waitForUpload:
true` for `no_workflow.webp` and a `waitForResponse` call for the
`/view` endpoint. This ensures the error toast (from the 500 response)
is consistently visible before the screenshot assertion.
## Review Focus
The fix is scoped to `no_workflow.webp` only (via a `filesWithUpload`
Set) since it's the only test file that triggers an upload + `/view`
call. Other media files embed workflows and don't hit this path.
Fixes#9450
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9458-test-fix-flaky-no_workflow-webp-screenshot-test-31b6d73d365081b88deaee91769baec1)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Enable `better-tailwindcss/enforce-consistent-class-order` lint rule and
auto-fix all 1027 violations across 263 files. Stacked on #9427.
## Changes
- **What**: Sort Tailwind classes into consistent order via `eslint
--fix`
- Enable `enforce-consistent-class-order` as `'error'` in eslint config
- Purely cosmetic reordering — no behavioral or visual changes
## Review Focus
Mechanical auto-fix PR — all changes are class reordering only. This is
the largest diff but lowest risk since it changes no class names, only
their order.
**Stack:** #9417 → #9427 → **this PR**
Fixes#9300 (partial — 3 of 3 rules)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9428-fix-enable-enforce-consistent-class-order-tailwind-lint-rule-31a6d73d3650811c9065f5178ba3e724)
by [Unito](https://www.unito.io)
## Summary
Addresses review feedback from PR #9298 and resolves the divergence
between `ResultItemImpl.isImage` and `appendCloudResParam`'s image
classification.
### Changes
- **Unify suffix-based classification**: Replace narrow
`isImageBySuffix` (gif/webp only), `isVideoBySuffix` (webm/mp4), and
`isAudioBySuffix` with `getMediaTypeFromFilename()` from
shared-frontend-utils, using the same `IMAGE_EXTENSIONS` set (png, jpg,
jpeg, gif, webp, bmp, avif, tif, tiff) that `appendCloudResParam` uses
- **imageCompare.ts**: Pass `record.filename` to `appendCloudResParam`
(was called without filename, bypassing image-extension guard)
- **imagePreviewStore.ts**: Use per-image `image.filename` instead of
first image's filename for all images in batch
- **LinearControls.vue**: Use `resultItem.filename` (already a string)
instead of `String(filename)` which converts undefined to `"undefined"`
### Related review comments
- [imageCompare.ts — missing
filename](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886137498)
- [imagePreviewStore.ts — per-image
filename](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886138718)
- [LinearControls.vue —
String(filename)](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886140159)
- [queueStore.ts — diverging image
classification](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886142886)
## Test plan
- [x] 66 unit tests pass (queueStore + cloudPreviewUtil)
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- Fixes#9386
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Enable `better-tailwindcss/enforce-canonical-classes` lint rule and
auto-fix all 611 violations across 173 files. Stacked on #9417.
## Changes
- **What**: Simplify Tailwind classes to canonical forms via `eslint
--fix`:
- `h-X w-X` → `size-X`
- `overflow-x-hidden overflow-y-hidden` → `overflow-hidden`
- and other canonical simplifications
- Enable `enforce-canonical-classes` as `'error'` in eslint config
## Review Focus
Mechanical auto-fix PR — all changes produced by `eslint --fix`. No
visual or behavioral changes; canonical forms are functionally
identical.
**Stack:** #9417 → **this PR** → PR 3 (class order)
Fixes#9300 (partial — 2 of 3 rules)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9427-fix-enable-enforce-canonical-classes-tailwind-lint-rule-31a6d73d365081a49340d7d4640ede45)
by [Unito](https://www.unito.io)
## Summary
Add workflow sharing by URL and a multi-step ComfyHub publish wizard,
gated by feature flags and an optional profile gate.
## Changes
- **What**: Share dialog with URL generation and asset warnings;
ComfyHub publish wizard (Describe → Examples → Finish) with thumbnail
upload and tags; profile gate flow; shared workflow URL loader with
confirmation dialog
- **Dependencies**: None (new `sharing/` module under
`src/platform/workflow/`)
## Review Focus
- Three new feature flags: `workflow_sharing_enabled`,
`comfyhub_upload_enabled`, `comfyhub_profile_gate_enabled`
- Share service API contract and stale-share detection
(`workflowShareService.ts`)
- Publish wizard and profile gate state management
- Shared workflow URL loading and query-param preservation
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8951-feat-share-workflow-by-URL-30b6d73d3650813ebbfafdad775bfb33)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
Add PostHog as a telemetry provider for cloud builds so custom events
can be correlated with session recordings. Follows the same pattern as
MixpanelTelemetryProvider with dynamic import, event queuing, and
disabled events from remote config. Tree-shaken away in OSS builds.
The posthog-js package uses Apache-2.0 (verified from its LICENSE file)
but declares it as "SEE LICENSE IN LICENSE" in package.json, which
the license checker can't parse.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9409-feat-Add-PostHog-telemetry-provider-31a6d73d3650818b8e86c772c6551099)
by [Unito](https://www.unito.io)
Add Storybook stories for WidgetInputText and WidgetTextarea, aligned
with the Figma Design System spec.
Task: COM-15821
## Summary
Add comprehensive Storybook stories for text widget components and
implement missing Figma design system variants for WidgetInputText.
## Changes
- **WidgetInputText component enhancements**:
- Add `size` prop (`medium` | `large`) matching Figma size variants
(32px / 40px)
- Add `invalid` prop with destructive border style per Figma Invalid
state
- Add `loading` prop showing spinning loader icon per Figma Status state
- Add hover background (`bg-component-node-widget-background-hovered`)
- Fix `readonly` not being applied from `widget.options.read_only`
- **WidgetTextarea component fixes**:
- Show copy button on hover for all states (not just read-only)
- Apply `text-component-node-foreground` token to copy icon
- Add hover background to wrapper
- **Storybook stories**:
- WidgetInputText: Default, Disabled, Invalid, Status, WithPlaceholder,
WithLabel stories
- WidgetTextarea: Default, Disabled, HiddenLabel, WithPlaceholder
stories
- Interactive controls for size, readOnly, disabled, invalid, loading
## Review Focus
- Figma alignment: size/invalid/loading/status variants for
WidgetInputText
- Copy icon color token (`text-component-node-foreground`) for
light/dark theme support
- `layoutWidget` computed pattern to merge `borderStyle` with invalid
state
## Screenshots (if applicable)
<!-- Add screenshots or video recording to help explain your changes -->
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Extract a shared `createMockWidget` test factory to eliminate duplicated
`SimplifiedWidget` object construction across 13 widget component test
files.
## Changes
- **What**: Add `widgetTestUtils.ts` with a generic
`createMockWidget<T>` factory providing sensible defaults (`name`,
`type`, `options`). Refactor 13 test files to delegate to it via thin
local wrappers that supply component-specific defaults (combo values,
slider ranges, etc.).
## Review Focus
- The shared factory only covers `SimplifiedWidget`-based tests. Three
files using different base types (`NodeWidgets.test.ts`,
`useRemoteWidget.test.ts`, `useComboWidget.test.ts`) are intentionally
excluded.
- `mountComponent` helpers remain per-file since plugin/component setups
vary too much to share.
Fixes#5554
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9423-refactor-extract-shared-createMockWidget-factory-for-widget-component-tests-31a6d73d36508159b65ee0e7b49212c3)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
## Summary
Fixes flaky screenshot test that was identified during the PR #9400
snapshot update, where baselines regenerated without any code changes
affecting them.
**Root cause:** `fillAndSelectFirstNode('KSampler')` selected `nth(0)`
from the autocomplete dropdown, but search result ordering is
non-deterministic when the search box opens via link release with filter
chips. Sometimes 'Preview Image' appeared as the first result instead of
'KSampler', causing a completely different node to be added.
**Fix:** Added an `exact: true` option to `fillAndSelectFirstNode` that
uses an `aria-label` selector to click the specific matching result
instead of blindly selecting the first item.
- Fixes#4658
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9426-fix-stabilize-flaky-screenshot-tests-for-search-results-and-image-preview-31a6d73d365081598167ce285416995c)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Rename `imagePreviewStore.ts` → `nodeOutputStore.ts` to match the store
it houses (`useNodeOutputStore`, Pinia ID `nodeOutput`).
## Changes
- **What**: Rename file + test file, update all 21 import paths, mock
paths, and describe labels
- **Breaking**: None — exported symbol (`useNodeOutputStore`) and Pinia
store ID (`nodeOutput`) are unchanged
## Custom Node Ecosystem Audit
Searched the ComfyUI custom node ecosystem for `imagePreviewStore` and
`useNodeOutputStore`:
- **Not part of the public API** — neither filename nor export appear in
`comfyui_frontend_package` or `vite.types.config.mts`
- **1 external repo found:** `wallen0322/ComfyUI-AE-Animation` —
contains a full fork of the frontend source tree; it copies the file
internally and does not import from the published package. **No
breakage.**
- **No custom nodes import this store via the extension API.** This is a
safe internal-only rename.
## Review Focus
Pure mechanical rename — no logic changes. Verify no stale
`imagePreviewStore` references remain.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9416-refactor-rename-imagePreviewStore-to-nodeOutputStore-31a6d73d3650816086c5e62959861ddb)
by [Unito](https://www.unito.io)
Co-authored-by: Alexander Brown <drjkl@comfy.org>
The pixels fade, the tests turn red,
Old screenshots haunt us from the dead.
A branch is born, a label placed,
And golden images are replaced.
The shards spin up, four workers strong,
To right what rendering got wrong.
When CI turns green, the deed is done—
New expectations, freshly won.
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Standalone WebGL2 rendering engine for client-side GLSL shader preview,
with utility functions that mirror the backend `nodes_glsl.py` detection
logic.
## Changes
- **What**: New `GLSLPreviewEngine` class (OffscreenCanvas + WebGL2),
`glslUtils.ts` (detectOutputCount, detectPassCount,
hasVersionDirective), and unit tests
- **GLSLPreviewEngine**: Fullscreen triangle via `gl_VertexID` (no VBO),
ping-pong FBOs for multi-pass rendering, MRT via `gl.drawBuffers()`,
blob output via `canvas.convertToBlob()`
- **glslUtils**: Pure functions ported from backend Python to
TypeScript, regex-based detection matching `_detect_output_count()` and
`_detect_pass_count()`
## Review Focus
- WebGL2 resource lifecycle (context loss, texture cleanup, FBO teardown
in `dispose()`)
- Ping-pong FBO logic for multi-pass shaders
- Engine tests are WebGL2-gated (`describe.skip` in happy-dom) — they
run in real browser environments
## Stacked PR
PR 2 of 3. Stacked on #9198 (fix: GLSLShader preview promotion).
PR 3: `feat/glsl-live-preview` (composable + LGraphNode.vue integration)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9200-feat-GLSLPreviewEngine-GLSL-utility-functions-3126d73d3650812fadc6df4a26387d0e)
by [Unito](https://www.unito.io)
## Summary
Adds handling for entering app mode with an empty graph prompting the
user to load a template as a starting point
## Changes
- **What**:
- app mode handle empty workflows, disable builder button, show
different message
- fix fitView when switching from app mode to graph
## Review Focus
Moving the fitView since the canvas is hidden in app mode until after
the workflow is loaded and the mode has been switched back to graph, I
don't see how this could cause any issues but worth a closer eye
## Screenshots (if applicable)
<img width="1057" height="916" alt="image"
src="https://github.com/user-attachments/assets/2ffe2b6d-9ce1-4218-828a-b7bc336c365a"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9393-feat-App-mode-empty-graph-handling-3196d73d3650812cab0ce878109ed5c9)
by [Unito](https://www.unito.io)
## Summary
When ComfyUI Manager is disabled, OSS local users see a hint in the
Missing Nodes panel explaining how to install and enable it.
## Changes
- **What**: Added an inline hint in `MissingNodeCard` that renders when
the user is on OSS (non-Cloud) and Manager is not active
(`showInfoButton` is false). The hint shows the pip install command and
the `--enable-manager` startup flag, formatted as inline `<code>`
snippets via `i18n-t` interpolation.
## Review Focus
- The `showManagerHint` computed is intentionally simple: `!isCloud &&
!props.showInfoButton`. `showInfoButton` is the existing signal for
whether Manager is available/enabled.
- Styling uses existing semantic tokens (`bg-comfy-menu-bg`,
`text-comfy-input-foreground`) to match the rest of the panel.
## Screenshot
<img width="642" height="452" alt="image"
src="https://github.com/user-attachments/assets/d08280d3-b4a0-4613-b092-1baa49f0b091"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9377-feat-add-manager-enable-hint-for-OSS-local-users-3196d73d365081a19037c8f55f11d1eb)
by [Unito](https://www.unito.io)
## Summary
Enable `better-tailwindcss/no-deprecated-classes` lint rule and auto-fix
all 103 violations across 65 files. First PR in a stacked series for
#9300.
## Changes
- **What**: Replace deprecated Tailwind v3 classes with v4 equivalents:
- `rounded` → `rounded-sm` (85)
- `flex-shrink-0` → `shrink-0` (16)
- `flex-grow` → `grow` (2)
- Enable `no-deprecated-classes` as `'error'` in eslint config
- Update one test asserting on `'rounded'` class string
## Review Focus
Mechanical auto-fix PR — all changes produced by `eslint --fix`. No
visual or behavioral changes (Tailwind v4 aliases these classes
identically).
Fixes#9300 (partial — 1 of 3 rules)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9417-fix-enable-no-deprecated-classes-tailwind-lint-rule-31a6d73d3650819eaef4cf8ad84fb186)
by [Unito](https://www.unito.io)
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Perf report workflow fails on fork PRs because `GITHUB_TOKEN` is
read-only for forks, causing "Resource not accessible by integration" on
the PR comment step.
## Changes
- **What**: Split `ci-perf-report.yaml` into a data-collection workflow
+ a `workflow_run`-triggered reporter (`pr-perf-report.yaml`), matching
the existing `ci-size-data`/`pr-size-report` pattern. Added fork PR
permissions guidance to `.github/AGENTS.md`.
- **ci-perf-report.yaml**: Removed the `report` job and `pull-requests:
write` permission. Added PR metadata (number + base branch) artifact
upload.
- **pr-perf-report.yaml** (new): Triggered by `workflow_run` on the perf
workflow. Downloads metrics + metadata artifacts, generates report,
posts PR comment with write permissions from the default-branch context.
## Review Focus
- The two-workflow split follows the same pattern as `ci-size-data.yaml`
→ `pr-size-report.yaml`, which already works for fork PRs.
- The `workflow_run` trigger runs in the base repo context per [GitHub
Security Lab
guidance](https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/),
so it safely has write permissions even for fork PRs.
- AGENTS.md guidance documents this pattern to prevent recurrence.
Fixes the failure seen in
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/22684230751/job/65763595989?pr=9380
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9382-fix-split-perf-report-workflow-for-fork-PR-support-3196d73d365081b29b35ed354e7789e2)
by [Unito](https://www.unito.io)
## Summary
Fix deterministic DOM widget clip-path rendering to resolve the flaky
"Can drag node" screenshot test.
## Root Cause
`useDomClipping.updateClipPath()` schedules clip-path calculation in a
`requestAnimationFrame`, but `DomWidget.vue`'s watcher reads
`clippingStyle.value` synchronously before the RAF fires. The stale
clip-path gets baked into `style.value` and never updated when the RAF
completes, causing the textarea DOM widget to non-deterministically
render in front of or behind the canvas-drawn node selection border.
## Fix
- Extract `composeStyle()` function and add a dedicated watcher on
`clippingStyle` that recomposes the final inline style whenever the
RAF-deferred clip-path updates
- Add `enableDomClipping` to the main watcher dependency array so
toggling the clipping setting immediately recomposes the style
- Add `moveMouseToEmptyArea()` call in the test as a secondary
stabilizer against hover highlight non-determinism
- Delete stale snapshot so CI regenerates it with correct clip-path
behavior
- Fixes#4658
---------
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Adds a collapse/expand all toggle button to all parameter and error tabs
in the right side panel, letting users quickly collapse or expand all
accordion sections at once.
## Changes
- **New component**: `CollapseToggleButton.vue` — a reusable icon button
(list-collapse / list-tree icon) with tooltip, bound via `v-model`
- **Error tab**: Toggle collapses/expands all error groups; per-group
state is now managed through `isSectionCollapsed` /
`setSectionCollapsed` helpers
- **Nodes tab** (`TabNodes.vue`): Per-node `collapseMap`; toggle
overrides per-section state; defaults to collapsed (nodes tab default
was already collapsed)
- **Normal inputs tab** (`TabNormalInputs.vue`): Per-node `collapseMap`
+ `advancedCollapsed`; toggle covers both normal and advanced sections;
defaults to collapsed when multiple nodes selected
- **Subgraph inputs tab** (`TabSubgraphInputs.vue`): Toggle covers both
main and advanced inputs sections
- **Global parameters tab** (`TabGlobalParameters.vue`): Toggle bound to
single `SectionWidgets`
- **i18n**: Added `g.collapseAll` and `g.expandAll` keys
## Review Focus
- `isAllCollapsed` getter in each tab: reads from the same per-section
state so the toggle accurately reflects current state rather than being
independently tracked
- `TabNormalInputs`: multi-node selection default collapse behaviour is
preserved through `isSectionCollapsed` fallback logic
## Screenshots
<img width="778" height="643" alt="image"
src="https://github.com/user-attachments/assets/04d07f32-5135-47f9-b029-78ca78a996fb"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9333-feat-add-collapse-expand-all-toggle-to-right-panel-tabs-3176d73d36508123ba22d6e81983bb1b)
by [Unito](https://www.unito.io)
## Summary
Add a reusable `SearchInput` component with theme-aware styling, clear
button, loading state, and configurable sizes.
## Changes
- **SearchInput.vue**: Composable search input wrapping Reka UI Combobox
with search/clear/loading icon states
- **searchInput.variants.ts**: CVA-based size variants (`sm`, `md`,
`lg`) using semantic theme tokens (`bg-secondary-background`,
`text-base-foreground`)
- **SearchInput.stories.ts**: Storybook coverage for all sizes, loading,
custom icon/placeholder, and background override
## Review Focus
- Clear button alignment with search icon (`left-3.5` for `icon-sm`
button vs `left-4` for `size-4` icon)
- Theme token choices for light/dark compatibility
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9168-feat-Add-reusable-SearchInput-component-3116d73d365081309290fe84a46852e4)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
- Add `quickRegister()` mappings for models being added in open cloud
PRs so the "Use" button works when those node packs ship
## Mappings added
| Cloud PR | Node Pack | Model Directories | Loader Node |
|----------|-----------|-------------------|-------------|
| [#2645](https://github.com/Comfy-Org/cloud/pull/2645) |
ComfyUI-HunyuanVideoWrapper | `LLM/llava-llama-3-8b-*` |
`DownloadAndLoadHyVideoTextEncoder` |
| [#2598](https://github.com/Comfy-Org/cloud/pull/2598) |
comfyui-cogvideoxwrapper | `CogVideo/GGUF`, `CogVideo/ControlNet` |
`DownloadAndLoadCogVideoGGUFModel`, `DownloadAndLoadCogVideoControlNet`
|
| [#2594](https://github.com/Comfy-Org/cloud/pull/2594) |
ComfyUI-DynamiCrafterWrapper | `checkpoints/dynamicrafter{,/controlnet}`
| `DownloadAndLoadDynamiCrafterModel`,
`DownloadAndLoadDynamiCrafterCNModel` |
| [#2537](https://github.com/Comfy-Org/cloud/pull/2537) |
ComfyUI_LayerStyle_Advance | `BEN`, `BiRefNet/pth`, `onnx/human-parts`,
`lama` | `LS_LoadBenModel`, `LS_LoadBiRefNetModel`,
`LS_HumanPartsUltra`, `LaMa` |
## Safe to merge before backend
Unknown node classes are silently skipped by `registerNodeProvider()` —
the mapping becomes a no-op until the node pack is deployed. Zero risk.
## Test plan
- [ ] Verify no runtime errors on load (unknown classes are skipped
gracefully)
- [ ] After backend PRs merge, verify "Use" button creates correct node
for each model type
🤖 Generated with [Claude Code](https://claude.com/claude-code)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9411-feat-add-model-to-node-backlinks-for-upcoming-custom-nodes-31a6d73d3650811bb129e557450f263a)
by [Unito](https://www.unito.io)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Follow-up to #9380. Replaces local `clone()` with shared util and
centralizes output snapshotting.
## Changes
- **What**: Replaced local `JSON.parse(JSON.stringify)` clone in
`changeTracker.ts` with shared `clone()` from `@/scripts/utils` (prefers
`structuredClone` with JSON fallback). Added `snapshotOutputs()` to
`useNodeOutputStore` as symmetric counterpart to existing
`restoreOutputs()`, and wired `changeTracker.store()` to use it.
- **Breaking**: None
## Review Focus
Symmetry between `snapshotOutputs()` and `restoreOutputs()` in the node
output store.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9387-refactor-changeTracker-use-shared-clone-util-and-centralize-nodeOutputs-snapshot-3196d73d365081a289c3cb414f57929e)
by [Unito](https://www.unito.io)
## Summary
Spin out workflow tab/load stability fixes from the share-by-url branch
so they can merge independently and reduce regression risk.
## Changes
- **What**: Fixes duplicate tabs on repeated same-workflow loads by
making active-workflow reload idempotent in `afterLoadNewGraph`; fixes
tab flicker on save/rename by removing async detach/attach gaps in
`workflowStore`; hardens duplicate workflow path by loading before clone
and assigning a new workflow `id`.
## Review Focus
Please review the idempotency gate in `afterLoadNewGraph`
(`activeState.id === workflowData.id`) and the save/rename path update
sequencing in `workflowStore` to confirm behavior remains correct for
restoration and re-import flows.
## Screenshots (if applicable)
N/A (workflow logic and tests only)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9345-fix-spin-out-workflow-tab-load-stability-regressions-3186d73d365081fe922bdc61dcf8d8f8)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
## Summary
Add missing `v-if="!compact"` guards to painter label divs so all labels
are hidden consistently in compact mode.
## Changes
- **What**: Added `v-if="!compact"` to the Color, Hardness, Width,
Height, and Background label divs in `WidgetPainter.vue`, matching the
existing guards on Tool and Size labels.
## Review Focus
Straightforward consistency fix — all 7 label divs now use the same
compact-mode guard.
Fixes#9235
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9397-fix-hide-all-painter-labels-in-compact-mode-consistently-31a6d73d3650811a9d3af8dd290e2bca)
by [Unito](https://www.unito.io)
## Summary
Adds model-to-node backlinks in `modelToNodeStore.ts` for all
cloud-deployed custom node models that were missing mappings. Without
these, clicking "Use" on a model in the model browser throws an error.
**17 new backlinks added** covering ~340 models across deployed node
packs:
| Category | Directories | Node | Models |
|----------|-------------|------|--------|
| Vision-Language | LLM/Qwen-VL/* (12 specific paths) | AILab_QwenVL /
AILab_QwenVL_PromptEnhancer | ~186 |
| TTS | qwen-tts/* | FB_Qwen3TTSVoiceClone | ~68 |
| Video | SEEDVR2, liveportrait/*, mimicmotion, rife | various | ~33 |
| Depth | depthanything3 | DownloadAndLoadDepthAnythingV3Model | 7 |
| Segmentation | face_parsing, sam3 | various | 4 |
| Diffusers | diffusers/* (Kolors) | DownloadAndLoadKolorsModel | 16 |
| Other | clip/*, dwpose, onnx, detection, UltraShape, sharp | various |
~26 |
**Key fix:** Replaced the top-level `LLM` fallback with specific
`LLM/Qwen-VL/*` paths. The old fallback incorrectly mapped `LLM/llava-*`
models to `AILab_QwenVL`.
Models without deployed node packs (llava/HyVideo, latentsync, sam3d,
sam3dbody, inpaint, vae_approx) are excluded — those are being removed
from `supported_models.json` in Comfy-Org/cloud#2652.
## Test plan
- [ ] Verify "Use" button works for QwenVL models in model browser
- [ ] Verify "Use" button works for TTS, video, depth, segmentation
models
- [ ] Verify no `No node provider registered for category` errors for
deployed models
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
## Summary
Replace fixed 10%/20% perf delta thresholds with dynamic σ-based
classification using z-scores, eliminating false alarms from naturally
noisy duration metrics (10-17% CV).
## Changes
- **What**:
- Run each perf test 3× (`--repeat-each=3`) and report the mean,
reducing single-run noise
- Download last 5 successful main branch perf artifacts to compute
historical μ/σ per metric
- Replace fixed threshold flags with z-score significance: `⚠️
regression` (z>2), `✅ neutral/improvement`, `🔇 noisy` (CV>50%)
- Add collapsible historical variance table (μ, σ, CV) to PR comment
- Graceful cold start: falls back to simple delta table until ≥2
historical runs exist
- New `scripts/perf-stats.ts` module with `computeStats`, `zScore`,
`classifyChange`
- 18 unit tests for stats functions
- **CI time impact**: ~3 min → ~5-6 min (repeat-each adds ~2 min,
historical download <10s)
## Review Focus
- The `gh api` call in the new "Download historical perf baselines"
step: it queries the last 5 successful push runs on the base branch. The
`gh` CLI is available natively on `ubuntu-latest` runners and
auto-authenticates with `GITHUB_TOKEN`.
- `getHistoricalStats` averages per-run measurements before computing
cross-run σ — this is intentional since historical artifacts may also
contain repeated measurements after this change lands.
- The `noisy` classification (CV>50%) suppresses metrics like `layouts`
that hover near 0 and have meaningless percentage swings.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9305-feat-add-statistical-significance-to-perf-report-with-z-score-thresholds-3156d73d3650818d9360eeafd9ae7dc1)
by [Unito](https://www.unito.io)
## Summary
Add `eslint-plugin-better-tailwindcss` to the ESLint toolchain for
Tailwind CSS v4 class linting.
## Changes
- **What**: Integrate `eslint-plugin-better-tailwindcss` (v4.3.1) with
the recommended config, pointed at the design-system CSS entry point for
v4 theme resolution. Five rules are enabled initially:
`enforce-canonical-classes`, `no-deprecated-classes`,
`no-conflicting-classes`, `no-duplicate-classes`,
`no-unnecessary-whitespace`. Three rules are disabled pending follow-up:
`no-unknown-classes` (needs PrimeIcon/custom class whitelisting),
`enforce-consistent-line-wrapping` (oxfmt conflict risk),
`enforce-consistent-class-order` (large batch change).
- **Dependencies**: `eslint-plugin-better-tailwindcss` ^4.3.1
- Fix conflicting `outline outline-1` classes in
`FormDropdownMenuActions.vue` (caught by the new
`no-conflicting-classes` rule).
## Review Focus
- Is the rule severity/enablement strategy appropriate for incremental
adoption?
- The 700 warnings (mostly `enforce-canonical-classes` and
`no-deprecated-classes`) are all auto-fixable via `eslint --fix` —
should we batch-fix them in this PR or a follow-up?
Fixes COM-15518
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9245-feat-add-eslint-plugin-better-tailwindcss-for-Tailwind-v4-linting-3136d73d365081df8a64dd55962d073f)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Replace hardcoded color and spacing values with semantic design tokens
and cache a computed variant class in StatusBadge.
## Changes
- **What**: Use Tailwind 4 CSS spacing variables in FormDropdownMenu
layout configs, replace zinc color utilities with semantic
`node-component-border` tokens in FormDropdownInput, wrap
`statusBadgeVariants()` in a `computed` for caching in StatusBadge.
## Review Focus
Straightforward token replacements and a computed caching change -- no
behavioral differences expected.
Fixes#9087Fixes#9086Fixes#7910
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9349-fix-replace-hardcoded-styles-with-design-tokens-and-cache-StatusBadge-variants-3186d73d36508185aae2e0753c9d1694)
by [Unito](https://www.unito.io)
Summary
- Add hidden setting `Comfy.Queue.ShowRunProgressBar` (default `true`).
- Add `Show run progress bar` toggle to the shared `...` job history
menu (`JobHistoryActionsMenu`), placed next to `Docked Job History`.
- Use that setting to control both the inline run progress bar and the
inline summary text under it.
- Keep queue button right-click context menu focused on queue actions.
- Add/update tests for the new toggle behavior and summary visibility.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9176-fix-add-run-progress-toggle-to-job-history-menu-3116d73d365081118202d8d67a857367)
by [Unito](https://www.unito.io)
## Summary
Cache `canvas.style.cursor` to avoid redundant DOM writes that dirty
Firefox's style tree.
## Changes
- **What**: Add `_lastCursor` field to
`LGraphCanvas._updateCursorStyle()` — only writes `canvas.style.cursor`
when the value changes. Eliminates ~347 redundant style mutations per
profiling session.
## Review Focus
- The fix is 2 lines (cache field + comparison). The unit test validates
the caching pattern without requiring full LGraphCanvas instantiation.
- This is one of several contributors to Firefox's cascading style
recalculation freeze. Each `canvas.style.cursor` write dirties the style
tree, which is flushed during the next paint in the canvas render loop.
## Stack
2 of 4 in Firefox perf fix stack. Depends on #9170.
<!-- Fixes #ISSUE_NUMBER -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9171-fix-cache-canvas-cursor-style-to-avoid-redundant-DOM-writes-3116d73d36508139827fe1d644fa1bd0)
by [Unito](https://www.unito.io)
## Summary
Replace `eval()` in `evaluateInput()` with a custom recursive descent
math parser, eliminating a security concern and enabling the `no-eval`
lint rule.
## Changes
- **New**: `mathParser.ts` — recursive descent parser for `+`, `-`, `*`,
`/`, `%`, `()`, decimals, unary operators. Zero new dependencies.
- **Modified**: `widget.ts` — replaced `eval()` call with
`evaluateMathExpression()`, use `isFinite()` instead of `isNaN()` to
reject `Infinity`
- **Modified**: `.oxlintrc.json` — `no-eval` rule changed from `"off"`
to `"error"`
- **Tests**: 59 parser tests + 23 integration tests covering complex
expressions, edge cases, and invalid input
## Review Feedback Addressed
- Renamed `unit()` → `primary()` for clarity
- Added modulo (`%`) operator support
- Normalized negative zero to positive zero
- Added depth limit (200) for nested parentheses
- Used `isFinite()` instead of `isNaN()` to reject
`Infinity`/`-Infinity`
- Added tests for edge-case number formats, unary-after-binary
operators, modulo, depth limits, scientific/hex notation, and `Infinity`
Fixes#8032Fixes#9272Fixes#9273Fixes#9274Fixes#9275
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9263-fix-Replace-eval-with-safe-math-expression-parser-3136d73d3650812f9f8dea21d1ea4f06)
by [Unito](https://www.unito.io)
## Summary
Narrow `CreateNodeOptions` from `Partial<Omit<LGraphNode, ...>>`
(exposing hundreds of properties/methods) to an explicit interface
listing only creation-time properties.
## Changes
- Replace `Partial<Omit<LGraphNode, 'constructor' | 'inputs' |
'outputs'>>` with explicit `CreateNodeOptions` interface containing
only: `pos`, `size`, `properties`, `flags`, `mode`, `color`, `bgcolor`,
`boxcolor`, `title`, `shape`, `inputs`, `outputs`
- Rename local `CreateNodeOptions` in `createModelNodeFromAsset.ts` to
`ModelNodeCreateOptions` to avoid collision
## Ecosystem verification
GitHub code search across ~50 repos confirms only `pos` and `outputs`
are used externally. All covered by the narrowed interface.
Fixes#9276Fixes#4740
## Summary
Sort execution error cards within each error group by their node
execution ID in ascending numeric order, ensuring consistent and
predictable display order.
## Changes
- **What**: Added `compareExecutionId` utility to
`src/types/nodeIdentification.ts` that splits node IDs on `:` and
compares segments numerically left-to-right; applied it as a sort
comparator when building `ErrorGroup.cards` in `useErrorGroups.ts`
## Review Focus
- The comparison treats missing segments as `0`, so `"1"` sorts before
`"1:20"` (subgraph nodes follow their parent); confirm this ordering
matches user expectations
- All comparisons are purely numeric — non-numeric segment values would
sort as `NaN` (treated as `0`)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9334-feat-error-groups-sort-execution-error-cards-by-node-execution-ID-3176d73d365081e1b3e4e4fa8831fe16)
by [Unito](https://www.unito.io)
A working branch of smaller app mode fixes. Can be merged at any time
and I'll make a new branch.
- Selected inputs and outputs can now be re-ordered when clicking on
label text
- 3d outputs once again display correctly
- Some padding has been added to the side so that control buttons don't
overlap with the floating app sidebar controls
- A "Share" button placeholder has been added to the menu, but is
disabled
- Adds a workaround for canvas read_only state being disabled when
'space' is pressed.
- This one is particularly hacky, and can be pulled out if problematic
- Fix download all only downloading the first output
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9368-Misc-app-mode-fixes-3196d73d365081eab02ad1e693784707)
by [Unito](https://www.unito.io)
## Summary
Make queue button tooltip mode-aware so it shows the correct action text
based on whether QPOV2 is enabled.
## Changes
- **What**: Update `queueHistoryTooltipConfig` in `ComfyActionbar.vue`
to conditionally show "View Job History" (QPOV2 enabled) or
"Expand/Collapse Queue" (QPOV2 disabled) instead of always showing "View
Job History"
## Review Focus
Straightforward conditional using existing `isQueuePanelV2Enabled`
computed and existing i18n keys.
Fixes#9278
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9350-fix-make-queue-button-tooltip-reflect-current-mode-3186d73d36508122b198e5fbb0226221)
by [Unito](https://www.unito.io)
## Summary
Change app mode changes to be written directly to the workflow on change
instead of requiring explicit save via builder.
Temporary: Adds `.app.json` file extension to app files for
identification since we don't currently have a way to identify them with
metadata
Removes app builder save dialog and replaces it with default mode
selection
## Changes
- **What**:
- ensure all save locations handle app mode
- remove dirtyLinearData and flushing
- **Breaking**:
- if people are relying on workflow names and are converting to/from app
mode in the same workflow, they will gain/lose the `.app` part of the
extension
## Screenshots (if applicable)
<img width="689" height="84" alt="image"
src="https://github.com/user-attachments/assets/335596ee-dce9-4e3a-a7b5-f0715c294e41"
/>
<img width="421" height="324" alt="image"
src="https://github.com/user-attachments/assets/ad3cd33c-e9f0-4c30-8874-d4507892fc6b"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9338-feat-App-mode-saving-rework-3176d73d3650813f9ae1f6c5a234da8c)
by [Unito](https://www.unito.io)
## Summary
- In cloud mode, large generated images (4K, 8K+) cause browser freezing
when loaded at full resolution for preview display
- The cloud backend (ingest service) now supports a `res` query
parameter on `/api/view` that returns server-side resized JPEG (quality
80, max 512px) instead of redirecting to the full-size GCS original
- This PR adds `&res=512` to all image preview URLs in cloud mode,
reducing browser decode overhead from tens of MB to tens of KB
- Downloads still use the original resolution (no `res` param)
- No impact on localhost/desktop builds (`isCloud` compile-time
constant)
### without `?res`
302 -> png downloads
<img width="808" height="564" alt="스크린샷 2026-02-28 오후 6 53 03"
src="https://github.com/user-attachments/assets/7c1c62dd-0bc4-468d-9c74-7b98e892e126"
/>
<img width="323" height="137" alt="스크린샷 2026-02-28 오후 6 52 52"
src="https://github.com/user-attachments/assets/926aa0c4-856c-4057-96a0-d8fbd846762b"
/>
200 -> jpeg
### with `?res`
<img width="811" height="407" alt="스크린샷 2026-02-28 오후 6 51 55"
src="https://github.com/user-attachments/assets/d58d46ae-6749-4888-8bad-75344c4d868b"
/>
### Changes
- **New utility**: `getCloudResParam(filename?)` returns `&res=512` in
cloud mode for image files, empty string otherwise
- **Core stores**: `imagePreviewStore` appends `res` to node output
URLs; `queueStore.ResultItemImpl` gets a `previewUrl` getter (separates
preview from download URLs)
- **Applied to**: asset browser thumbnails, widget dropdown previews,
linear mode indicators, image compare node, background image upload
### Intentionally excluded
- Downloads (`getAssetUrl`) — need original resolution
- Mask editor — needs pixel-accurate data
- Audio/video/3D files — `res` only applies to raster images
- Execution-in-progress previews — use WebSocket blob URLs, not
`/api/view`
## Test plan
- [x] Unit tests for `getCloudResParam()` (5 tests: cloud/non-cloud,
image/non-image, undefined filename)
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] All 5332 unit tests pass
- [x] Manual verification on cloud.comfy.org: `res=512` returns 200 with
resized JPEG; without `res` returns 302 redirect to GCS PNG original
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- The input and output indicators are now plugged directly into the
`LGraphNode.vue` template. Care was taken to make implementation to have
low cost for performance and complexity when not in app mode setup.
- Context menu event handlers are added to each widget in vue mode
instead of resolving the target widget of an event
- Swap the nodeId passed by `useGraphNodeManager` to not include the
locator id. This id was never used and was incorrect since it didn't
resolve across nested subgraphs.
- Continued bug fixes for app mode as a whole.
Known issue: There is disparity of nodeId between litegraph (which
references the widget in the root graph) and vue (which promotes the
original widget). Efforts to reconcile are ongoing.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9259-Support-selection-app-inputs-and-outputs-from-vue-mode-3136d73d365081ae8e56e35bf6322409)
by [Unito](https://www.unito.io)
---------
Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
## Summary
Add persistent upgrade CTAs for free-tier users: a topbar button and
"Upgrade to add credits" replacing "Add Credits" in popovers and
settings panels.
## Changes
- **What**:
- New `TopbarSubscribeButton` component in both GraphCanvas and
LinearView topbars, visible only to free-tier users
- Profile popover (legacy + workspace): free-tier users see "Upgrade to
add credits" instead of "Add Credits", linking directly to the pricing
table
- Manage Plan settings (legacy + workspace): same replacement —
free-tier users see "Upgrade to add credits" instead of "Add Credits"
- Paid-tier users retain the original "Add Credits" behavior in all
locations
- All upgrade buttons go directly to the pricing table (one-step flow)
## Review Focus
- The `isFreeTier` conditional gating on the buttons — ensure free-tier
users see upgrade CTAs and paid users see normal Add Credits
- Layout in Manage Plan panels uses `flex flex-col gap-3` to stack the
upgrade button below the usage history link instead of side-by-side
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9315-feat-add-ever-present-upgrade-button-for-free-tier-users-3166d73d365081228cdfe6a67fec6aec)
by [Unito](https://www.unito.io)
## Summary
Adds `.github/workflows/cloud-dispatch-build.yaml` — fires a
`repository_dispatch` event (`frontend-asset-build`) to
`Comfy-Org/cloud` on push to `cloud/*` branches and `main`.
The cloud repo handles the actual build, GCS upload, and secret
management (Sentry, Algolia, GCS creds). This is fire-and-forget.
## Changes
- New workflow: `cloud-dispatch-build.yaml`
- Trigger: `push` to `cloud/*` and `main` only
- Payload: `ref` (commit SHA) + `branch` (branch name), built with `jq`
to prevent injection
- SHA-pinned `peter-evans/repository-dispatch@v4.0.1`
- Hardened: `permissions: {}`, fork guard (`if: github.repository ==
'Comfy-Org/ComfyUI_frontend'`), concurrency to avoid dispatch storms
- `cloud-deploy-frontend.yaml` left unchanged (still needed during
migration)
## Setup Required
A repository secret `CLOUD_DISPATCH_TOKEN` must be configured — see PR
description comments.
## Part of
Frontend separate deploy prep (Task 1.3)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9308-feat-add-cloud-frontend-build-dispatch-workflow-3156d73d36508164a515eb968f6c5d79)
by [Unito](https://www.unito.io)
## Summary
Fix multiple issues with promoted widget resolution in nested subgraphs,
ensuring correct value propagation, slot matching, and rendering for
deeply nested promoted widgets.
## Changes
- **What**: Stabilize nested subgraph promoted widget resolution chain
- Use deep source keys for promoted widget values in Vue rendering mode
- Resolve effective widget options from the source widget instead of the
promoted view
- Stabilize slot resolution for nested promoted widgets
- Preserve combo value rendering for promoted subgraph widgets
- Prevent subgraph definition deletion while other nodes still reference
the same type
- Clean up unused exported resolution types
## Review Focus
- `resolveConcretePromotedWidget.ts` — new recursive resolution logic
for deeply nested promoted widgets
- `useGraphNodeManager.ts` — option extraction now uses
`effectiveWidget` for promoted widgets
- `SubgraphNode.ts` — unpack no longer force-deletes definitions
referenced by other nodes
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9282-fix-stabilize-nested-subgraph-promoted-widget-resolution-3146d73d365081208a4fe931bb7569cf)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
## Summary
Implement 11 Figma design discrepancies for the Node Library sidebar and
V2 Node Search dialog, aligning the UI with the [Toolbox Figma
design](https://www.figma.com/design/xMFxCziXJe6Denz4dpDGTq/Toolbox?node-id=2074-21394&m=dev).
## Changes
- **What**: Sidebar: reorder tabs (All/Essentials/Blueprints), rename
Custom→Blueprints, uppercase section headers, chevron-left of folder
icon, bookmark-on-hover for node rows, filter dropdown with checkbox
items, sort labels (Categorized/A-Z) with label-left/check-right layout,
hide section headers in A-Z mode. Search dialog: expand filter chips
from 3→6, add Recents and source categories to sidebar, remove "Filter
by" label. Pull foundation V2 components from merged PR #8548.
- **Dependencies**: Depends on #8987 (V2 Node Search) and #8548
(NodeLibrarySidebarTabV2)
## Review Focus
- Filter dropdown (`filterOptions`) is UI-scaffolded but not yet wired
to filtering logic (pending V2 integration)
- "Recents" category currently returns frequency-based results as
placeholder until a usage-tracking store is implemented
- Pre-existing type errors from V2 PR dependencies not in the base
commit (SearchBoxV2, usePerTabState, TextTicker, getProviderIcon,
getLinkTypeColor, SidebarContainerKey) are expected and will resolve
when rebased onto main after parent PRs land
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9085-feat-Node-Library-sidebar-and-V2-Search-dialog-Figma-design-improvements-30f6d73d36508175bf72d716f5904476)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Yourz <crazilou@vip.qq.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Resolves six open issues by reorganizing node replacement components
into a domain-driven folder structure, refactoring event handling to
follow the emit pattern, and adding comprehensive test coverage across
all affected modules.
## Changes
- **What**:
- Moved `SwapNodeGroupRow.vue` and `SwapNodesCard.vue` from
`src/components/rightSidePanel/errors/` to
`src/platform/nodeReplacement/components/` (Issues #9255)
- Moved `useMissingNodeScan.ts` from `src/composables/` to
`src/platform/nodeReplacement/missingNodeScan.ts`, renamed to reflect it
is a plain function not a Vue composable (Issues #9254)
- Refactored `SwapNodeGroupRow.vue` to emit a `'replace'` event instead
of calling `useNodeReplacement()` and `useExecutionErrorStore()`
directly; replacement logic now handled in `TabErrors.vue` (Issue #9267)
- Added unit tests for `removeMissingNodesByType`
(`executionErrorStore.test.ts`), `scanMissingNodes`
(`missingNodeScan.test.ts`), and `swapNodeGroups` computed
(`swapNodeGroups.test.ts`, `useErrorGroups.test.ts`) (Issue #9270)
- Added placeholder detection tests covering unregistered-type detection
when `has_errors` is false, and exclusion of registered types
(`useNodeReplacement.test.ts`) (Issue #9271)
- Added component tests for `MissingNodeCard` and `MissingPackGroupRow`
covering rendering, expand/collapse, events, install states, and edge
cases (Issue #9231)
- Added component tests for `SwapNodeGroupRow` and `SwapNodesCard`
(Issues #9255, #9267)
## Additional Changes (Post-Review)
- **Edge case guard in placeholder detection**
(`useNodeReplacement.ts`): When `last_serialization.type` is absent (old
serialization format), the predicate falls back to `n.type`, which the
app may have already run through `sanitizeNodeName` — stripping HTML
special characters (`& < > " ' \` =`). In that case, a `Set.has()`
lookup against the original unsanitized type name would silently miss,
causing replacement to be skipped.
Fixed by including sanitized variants of each target type in the
`targetTypes` Set at construction time. For the overwhelmingly common
case (no special characters in type names), the Set deduplicates the
entries and runtime behavior is identical to before.
A regression test was added to cover the specific scenario:
`last_serialization.type` absent + live `n.type` already sanitized.
## Review Focus
- `TabErrors.vue`: confirm the new `@replace` event handler correctly
replaces nodes and removes them from missing nodes list (mirrors the old
inline logic in `SwapNodeGroupRow`)
- `missingNodeScan.ts`: filename/export name change from
`useMissingNodeScan` — verify all call sites updated via `app.ts`
- Test mocking strategy: module-level `vi.mock()` factories use closures
over `ref`/plain objects to allow per-test overrides without global
mutable state
- Fixes#9231
- Fixes#9254
- Fixes#9255
- Fixes#9267
- Fixes#9270
- Fixes#9271
## Summary
Fixes two bugs in the node replacement flow: placeholder detection
failing after workflow execution or pack reinstallation, and missing UI
sync in the Errors Tab when replacements are applied from the modal
dialog.
## Changes
- **Placeholder detection**: Node placeholder detection now matches
against `targetTypes` (derived from the replaceable node list built at
workflow load time) instead of relying on `has_errors` flag or
`registered_node_types` lookup. This ensures replacement works reliably
after execution (where `has_errors` gets cleared) and after pack
reinstallation (where the type becomes registered).
- **Modal → Errors Tab sync**: Added
`executionErrorStore.removeMissingNodesByType()` call in
`MissingNodesContent.vue` after replacement, so the Errors Tab reflects
changes immediately without requiring a page reload.
## Review Focus
- `collectAllNodes` predicate change in `useNodeReplacement.ts`: now
uses `targetTypes.has(originalType)` to find nodes by their original
serialized type. This is independent of runtime state like `has_errors`
or `registered_node_types`.
- `executionErrorStore.removeMissingNodesByType` call timing in
`MissingNodesContent.vue` — runs synchronously after
`replaceNodesInPlace` resolves, before auto-close logic.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9269-fix-node-replacement-fails-after-execution-and-modal-sync-3146d73d365081218398c961639b450f)
by [Unito](https://www.unito.io)
## Summary
Pre-rasterize the SubgraphNode SVG icon to a bitmap canvas to eliminate
Firefox's per-frame SVG style processing.
## Changes
- **What**: Add `getWorkflowBitmap()` that lazily rasterizes the
`data:image/svg+xml` workflow icon to an `HTMLCanvasElement` (16×16) on
first use. `SubgraphNode.drawTitleBox()` draws the cached bitmap instead
of the raw SVG.
## Review Focus
- Firefox re-processes SVG internal stylesheets (`stroke`,
`stroke-linecap`, `stroke-width`) every time `ctx.drawImage(svgImage)`
is called. Chrome caches the rasterization. This happens on every frame
for every visible SubgraphNode.
- Reporter confirmed strong subgraph correlation: "it may be happening
in the default workflow with subgraph" / "didn't seem to happen just
using manually wired up diffusion loader, clip, sampler, etc."
- Falls back to the raw SVG Image if not yet loaded or if
`getContext('2d')` returns null.
## Stack
3 of 4 in Firefox perf fix stack. Depends on #9170.
<!-- Fixes #ISSUE_NUMBER -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9172-fix-pre-rasterize-SubgraphNode-SVG-icon-to-bitmap-canvas-3116d73d365081babf17cf0848d37269)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Prevent latent previews received after the job/node has already finished
processing overwriting the actual output display
## Changes
- **What**:
- updates job preview store to also track which node the preview was for
- updates linear progress tracking to store executed nodes enabling
skipping previews of these
## Review Focus
<!-- Critical design decisions or edge cases that need attention -->
<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->
## Screenshots (if applicable)
<!-- Add screenshots or video recording to help explain your changes -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9261-App-mode-discard-slow-preview-messages-to-prevent-overwriting-output-image-3136d73d3650817884c2ce2ff5993b9e)
by [Unito](https://www.unito.io)
## Summary
Follow-up to #9215 to keep Docked Job History toggle behavior
deterministic even when settings persistence fails.
## Changes
- Close the actions popover immediately when toggling Docked Job
History.
- Use settingStore.setMany(...) when switching from docked to floating
mode.
- Set sidebarTabStore.activeSidebarTabId = 'job-history' before
persisting when switching from floating to docked mode.
- Wrap persistence calls in try/catch without rollback so
locally-applied UI state remains deterministic.
- Expand QueueOverlayHeader tests to cover setMany, popover close
behavior, and persistence-failure paths.
## Testing
- pnpm test:unit -- src/components/queue/QueueOverlayHeader.test.ts
- pnpm typecheck
- pnpm exec eslint src/components/queue/JobHistoryActionsMenu.vue
src/components/queue/QueueOverlayHeader.test.ts
- pnpm lint (fails in this branch due pre-existing stylelint errors in
generated apps/desktop-ui/dist/**/*.css files, unrelated to this change)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9265-fix-make-docked-job-history-toggle-persistence-safe-3146d73d3650818f86c4dfdd57669abd)
by [Unito](https://www.unito.io)
## Summary
Batch `getBoundingClientRect()` calls in `updateClipPath` via
`requestAnimationFrame` to avoid forced synchronous layout.
## Changes
- **What**: Wrap the layout-reading portion of `updateClipPath` in
`requestAnimationFrame()` with cancellation. Multiple rapid calls within
the same frame are coalesced into a single layout read. Eliminates
~1,053 forced synchronous layouts per profiling session.
## Review Focus
- `getBoundingClientRect()` forces synchronous layout. When interleaved
with style mutations (from PrimeVue `useStyle`, cursor writes, Vue VDOM
patching), this creates layout thrashing — especially in Firefox where
Stylo aggressively invalidates the entire style cache.
- The RAF wrapper coalesces all calls within a frame into one, reading
layout only once per frame. The `cancelAnimationFrame` ensures only the
latest parameters are used.
- `willChange: 'clip-path'` is included to hint the browser to optimize
clip-path animations.
## Stack
4 of 4 in Firefox perf fix stack. Depends on #9170.
<!-- Fixes #ISSUE_NUMBER -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9173-fix-batch-updateClipPath-via-requestAnimationFrame-3116d73d3650810392f7fba7ea5ceb6f)
by [Unito](https://www.unito.io)
## Summary
Wire `essentials_category` through from backend to the Essentials tab
UI. Creates a single source of truth for node categorization and
ordering.
### Changes
**New file — `src/constants/essentialsNodes.ts`:**
- Single source of truth: `ESSENTIALS_NODES` (ordered nodes per
category), `ESSENTIALS_CATEGORIES` (folder display order),
`ESSENTIALS_CATEGORY_MAP` (flat lookup), `TOOLKIT_NOVEL_NODE_NAMES`
(telemetry), `TOOLKIT_BLUEPRINT_MODULES`
**Refactored files:**
- `src/types/nodeSource.ts`: Removed inline `ESSENTIALS_CATEGORY_MOCK`,
imports `ESSENTIALS_CATEGORY_MAP` from centralized constants
- `src/services/nodeOrganizationService.ts`: Removed inline
`NODE_ORDER_BY_FOLDER`, imports `ESSENTIALS_NODES` and
`ESSENTIALS_CATEGORIES`
- `src/constants/toolkitNodes.ts`: Re-exports from `essentialsNodes.ts`
instead of maintaining a separate list
**Subgraph passthrough:**
- `src/stores/subgraphStore.ts`: Passes `essentials_category` from
`GlobalSubgraphData` and extracts it from `definitions.subgraphs[0]` as
fallback
- `src/platform/workflow/validation/schemas/workflowSchema.ts`: Added
`essentials_category` to `SubgraphDefinitionBase` and
`zSubgraphDefinition`
**Tests:**
- `src/constants/essentialsNodes.test.ts`: 6 tests validating no
duplicates, complete coverage, basics exclusion
- `src/stores/subgraphStore.test.ts`: 2 tests for essentials_category
passthrough
All 43 relevant tests pass. Typecheck, lint, format clean.
**Depends on:** Comfy-Org/ComfyUI#12573
Fixes COM-15221
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9091-feat-wire-essentials_category-for-Essentials-tab-display-30f6d73d3650814ab3d4c06b451c273b)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Rename `lastHistoryQueueIndex` → `lastJobHistoryPriority` and
`currentQueueIndex` → `currentJobPriority` to reflect that these
variables now read `job.priority` directly.
## Changes
- **queueStore.ts**: `lastHistoryQueueIndex` → `lastJobHistoryPriority`
- **JobDetailsPopover.vue**: `currentQueueIndex` → `currentJobPriority`
- **queueStore.test.ts**: Updated references and test descriptions
Fixes#9246
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9258-refactor-Rename-queueIndex-variables-to-reflect-job-priority-usage-3136d73d36508126989dd464f7dad9a1)
by [Unito](https://www.unito.io)
## Summary
Adds a node replacement UI to the Errors Tab so users can swap missing
nodes with compatible alternatives directly from the error panel,
without opening a separate dialog.
## Changes
- **What**: New `SwapNodesCard` and `SwapNodeGroupRow` components render
swap groups in the Errors Tab; each group shows the missing node type,
its instances (with locate buttons), and a Replace button. Added
`useMissingNodeScan` composable to scan the graph for missing nodes and
populate `executionErrorStore`. Added `removeMissingNodesByType()` to
`executionErrorStore` so replaced nodes are pruned from the error list
reactively.
## Bug Fixes Found During Implementation
### Bug 1: Replaced nodes render as empty shells until page refresh
`replaceWithMapping()` directly mutates `_nodes[idx]`, bypassing the Vue
rendering pipeline entirely. Because the replacement node reuses the
same ID, `vueNodeData` retains the stale entry from the old placeholder
(`hasErrors: true`, empty widgets/inputs). `graph.setDirtyCanvas()` only
repaints the LiteGraph canvas and has no effect on Vue.
**Fix**: After `replaceWithMapping()`, manually call
`nodeGraph.onNodeAdded?.(newNode)` to trigger `handleNodeAdded` in
`useGraphNodeManager`, which runs `extractVueNodeData(newNode)` and
updates `vueNodeData` correctly. Also added a guard in `handleNodeAdded`
to skip `layoutStore.createNode()` when a layout for the same ID already
exists, preventing a duplicate `spatialIndex.insert()`.
### Bug 2: Missing node error list overwritten by incomplete server
response
Two compounding issues: (A) the server's `missing_node_type` error only
reports the *first* missing node — the old handler parsed this and
called `surfaceMissingNodes([singleNode])`, overwriting the full list
collected at load time. (B) `queuePrompt()` calls `clearAllErrors()`
before the API request; if the subsequent rescan used the stale
`has_errors` flag and found nothing, the missing nodes were permanently
lost.
**Fix**: Created `useMissingNodeScan.ts` which scans
`LiteGraph.registered_node_types` directly (not `has_errors`). The
`missing_node_type` catch block in `app.ts` now calls
`rescanAndSurfaceMissingNodes(this.rootGraph)` instead of parsing the
server's partial response.
## Review Focus
- `handleReplaceNode` removes the group from the store only when
`replaceNodesInPlace` returns at least one replaced node — should we
always clear, or only on full success?
- `useMissingNodeScan` re-scans on every execution-error change; confirm
no performance concerns for large graphs with many subgraphs.
## Screenshots
https://github.com/user-attachments/assets/78310fc4-0424-4920-b369-cef60a123d50https://github.com/user-attachments/assets/3d2fd5e1-5e85-4c20-86aa-8bf920e86987
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9253-feat-add-node-replacement-UI-to-Errors-Tab-3136d73d365081718d4ddfd628cb4449)
by [Unito](https://www.unito.io)
## Summary
Make the Docked Job History toggle deterministic so it opens the
expected UI target in both directions.
## Changes
- Update `JobHistoryActionsMenu` toggle behavior:
- When currently docked (`Comfy.Queue.QPOV2=true`), disable docked mode
and explicitly open floating QPO (`Comfy.Queue.History.Expanded=true`)
- When currently floating (`Comfy.Queue.QPOV2=false`), enable docked
mode and open the `job-history` sidebar tab
- Add/adjust unit tests in `QueueOverlayHeader.test.ts` to verify both
toggle directions and target panel behavior
## Testing
- `pnpm exec eslint src/components/queue/JobHistoryActionsMenu.vue
src/components/queue/QueueOverlayHeader.test.ts`
- `pnpm typecheck`
- `pnpm test:unit -- src/components/queue/QueueOverlayHeader.test.ts`
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9215-fix-open-target-panel-when-toggling-Docked-Job-History-3126d73d3650810eb409ff38e3a521f3)
by [Unito](https://www.unito.io)
## Summary
- fix sizing of sidebars in app mode
- update feedback button to match design
- update job queue notification
- clickable queue spinner item to allow clear queue
- refactor mode out of store to specific workflow instance
- support different saved vs active mode
- other styling/layout tweaks
## Changes
- **What**: Changes the store to a composable and moves the mode state
to the workflow.
- This enables switching between tabs and maintaining the mode they were
in
## Screenshots (if applicable)
<img width="1866" height="1455" alt="image"
src="https://github.com/user-attachments/assets/f9a8cd36-181f-4948-b48c-dd27bd9127cf"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9137-App-mode-more-updates-fixes-3106d73d365081a18ccff6ffe24fdec7)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
### Motivation
- Subscription credit labels were rendering the refill date with
HTML-escaped separators (`/`) because `vue-i18n` parameter escaping
was applied to the date interpolation.
- The goal is to render date-only parameters like `MM/DD/YY` with
literal slashes so the UI shows a human-readable date string.
### Description
- Disabled `vue-i18n` parameter escaping for the
`subscription.creditsRemainingThisMonth` and
`subscription.creditsRemainingThisYear` lookups in both subscription
panels by passing `{ escapeParameter: false }` to `t()` in
`SubscriptionPanelContentLegacy.vue` and
`SubscriptionPanelContentWorkspace.vue`.
- Adjusted the unit test i18n setup in `SubscriptionPanel.test.ts` to
include `escapeParameter: true` in the test `i18n` instance and updated
the test messages to use `Included (Refills {date})`.
- Added a regression unit test in `SubscriptionPanel.test.ts` asserting
the rendered label contains `Included (Refills 12/31/24)` and does not
contain the escaped entity `/`.
### Testing
- Ran formatting with `pnpm format` which completed successfully.
- Ran lint via `pnpm lint` which passed with pre-existing warnings only
(no new errors).
- Ran type checking with `pnpm typecheck` (via `vue-tsc --noEmit`) which
completed successfully.
- Ran the modified unit tests with `pnpm vitest run
src/platform/cloud/subscription/components/SubscriptionPanel.test.ts`
and the test file passed (10 passed, 5 skipped).
- Attempted a Playwright-based visual capture of the running app but
Chromium crashed in this environment (SIGSEGV) before navigation, so no
screenshot was produced.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_69a0175f58788330b2256329a500e14b)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9251-fix-preserve-refill-date-slashes-in-subscription-credits-label-3136d73d36508182b770f5719a52d189)
by [Unito](https://www.unito.io)
2026-02-26 09:37:03 -08:00
928 changed files with 39855 additions and 7455 deletions
description: Reviews UI code for WCAG 2.2 AA accessibility violations
severity-default: medium
tools: [Read, Grep]
---
You are an accessibility auditor reviewing a code diff for WCAG 2.2 AA compliance. Focus on UI changes that affect users with disabilities.
Check for:
1.**Missing form labels** - inputs, selects, textareas without associated `<label>` or `aria-label`/`aria-labelledby`
2.**Missing alt text** - images without `alt` attributes, or decorative images missing `alt=""`
3.**Keyboard navigation** - interactive elements not focusable, custom widgets missing keyboard handlers (Enter, Space, Escape, Arrow keys), focus traps without escape
4.**Focus management** - modals/dialogs that don't trap focus, dynamic content that doesn't move focus appropriately, removed elements without focus recovery
5.**ARIA misuse** - invalid `aria-*` attributes, roles without required children/properties, `aria-hidden` on focusable elements
6.**Color as sole indicator** - using color alone to convey meaning (errors, status) without text/icon alternative
7.**Touch targets** - interactive elements smaller than 24x24 CSS pixels (WCAG 2.2 SC 2.5.8)
8.**Screen reader support** - dynamic content changes without `aria-live` announcements, unlabeled icon buttons, links with only "click here"
- Skip canvas-based content: the LiteGraph node editor renders on `<canvas>` elements, not DOM-based UI. WCAG rules don't fully apply to canvas rendering internals — only audit the DOM-based controls around it (toolbars, panels, dialogs)
- "Critical" for completely inaccessible interactive elements, "major" for missing labels/ARIA, "minor" for enhancement opportunities
description: Catches breaking changes to public interfaces, window-exposed APIs, event contracts, and exported symbols
severity-default: high
tools: [Grep, Read, glob]
---
You are an API contract reviewer. Your job is to catch breaking changes and contract violations in public-facing interfaces.
## What to Check
1.**Breaking changes to globally exposed APIs** — anything on `window` or other global objects that consumers depend on. Renamed properties, removed methods, changed signatures, changed return types.
2.**Event contract changes** — renamed events, changed event payloads, removed events that listeners may depend on.
3.**Changed function signatures** — parameters reordered, required params added, return type changed on exported functions.
4.**Removed or renamed exports** — any `export` that was previously available and is now gone or renamed without a re-export alias.
5.**REST API changes** — changed endpoints, added required fields, removed response fields, changed status codes.
6.**Type contract narrowing** — a function that used to accept `string | number` now only accepts `string`, or a return type that narrows unexpectedly.
7.**Default value changes** — changing defaults on optional parameters that consumers may rely on.
8.**Store/state shape changes** — renamed store properties, changed state structure that computed properties or watchers may depend on.
## How to Identify the Public API
- Check `package.json` for `"exports"` or `"main"` fields.
- **Window globals**: This repo exposes LiteGraph classes on `window` (e.g., `window['LiteGraph']`, `window['LGraphNode']`, `window['LGraphCanvas']`) and `window['__COMFYUI_FRONTEND_VERSION__']`. These are consumed by custom node extensions and must not be renamed or removed.
- **Extension hooks**: The `app` object and its extension registration system (`app.registerExtension`) is a public contract for third-party custom nodes. Changes to `ComfyApp`, `ComfyApi`, or the extension lifecycle are breaking changes.
- Check AGENTS.md for project-specific API surface definitions.
- Any exported symbol from common entry points (e.g., `src/types/index.ts`) should be treated as potentially public.
## Rules
- ONLY flag changes that break existing consumers.
- Do NOT flag additions (new methods, new exports, new endpoints).
- Do NOT flag internal/private API changes.
- Always check if a re-export or compatibility shim was added before flagging.
- Critical for removed/renamed globals, high for changed export signatures, medium for changed defaults.
description: Reviews code for excessive complexity and suggests refactoring opportunities
severity-default: medium
tools: [Grep, Read, glob]
---
You are a complexity and refactoring advisor reviewing a code diff. Focus on code that is unnecessarily complex and will be hard to maintain.
## What to Check
1.**High cyclomatic complexity** — functions with many branching paths (if/else chains, switch statements with >7 cases, nested ternaries). Threshold: complexity >10 is high severity, >15 is critical.
2.**Deep nesting** — code nested >4 levels deep (nested if/for/try blocks). Suggest guard clauses, early returns, or extraction.
3.**Oversized functions** — functions >50 lines that do multiple things. Suggest extraction of cohesive sub-functions.
5.**Long parameter lists** — functions with >5 parameters. Suggest parameter objects or builder patterns.
6.**Complex boolean expressions** — conditions with >3 clauses that are hard to parse. Suggest extracting to named boolean variables.
7.**Feature envy** — methods that use data from another class more than their own, suggesting the method belongs elsewhere.
8.**Duplicate logic** — two or more code blocks in the diff doing essentially the same thing with minor variations.
9.**Unnecessary indirection** — wrapper functions that add no value, abstractions for single-use cases, premature generalization.
## Rules
- Only flag complexity in NEW or SIGNIFICANTLY CHANGED code.
- Do NOT suggest refactoring stable, well-tested code that happens to be complex.
- Do NOT flag complexity that is inherent to the problem domain (e.g., state machines, protocol handlers).
- Provide a concrete refactoring approach, not just "this is too complex".
- High severity for code that will likely cause bugs during future modifications, medium for readability improvements, low for optional simplifications.
description: Reviews whether new code is placed in the right domain/layer and follows domain-driven structure principles
severity-default: medium
tools: [Grep, Read, glob]
---
You are a domain-driven design reviewer. Your job is to check whether new or moved code is placed in the correct architectural layer and domain folder.
## Principles
1.**Domain over Technical Layer** — code should be organized by what it does (domain/feature), not by what it is (component/service/store). New files in flat technical folders like `src/components/`, `src/services/`, `src/stores/`, `src/utils/` are a smell if the repo already has domain folders.
2.**Cohesion** — files that change together should live together. A component, its store, its service, and its types for a single feature should be co-located.
3.**Import Direction** — lower layers must not import from higher layers. Check that imports flow in the allowed direction (see Layer Architecture below).
4.**Bounded Contexts** — each domain/feature should have clear boundaries. Cross-domain imports should go through public interfaces, not reach into internal files.
5.**Naming** — folders and files should reflect domain concepts, not technical roles. `workflowExecution.ts` > `service.ts`.
## Layer Architecture
This repo uses a VSCode-style layered architecture with strict unidirectional imports:
Flag NEW files added to these legacy flat folders (they should go in a domain folder under the appropriate layer instead):
-`src/components/` → should be in `src/renderer/` or `src/workbench/extensions/{feature}/components/`
-`src/stores/` → should be in `src/platform/{domain}/` or `src/workbench/extensions/{feature}/stores/`
-`src/services/` → should be in `src/platform/{domain}/`
-`src/composables/` → should be in `src/renderer/` or `src/platform/{domain}/ui/`
Do NOT flag modifications to existing files in legacy folders — only flag NEW files.
## How to Review
1. Look at the diff to see where new files are created or where code is added.
2. Check if the repo has an established domain folder structure (look for domain-organized directories like `src/platform/`, `src/workbench/`, `src/renderer/`, `src/base/`, or similar).
3. If domain folders exist but new code was placed in a flat technical folder, flag it.
4. Run import direction checks:
- Use `Grep` or `Read` to check if new imports violate layer boundaries.
- Flag any imports from a higher layer to a lower one using the rules above.
5. Check for new files in legacy flat folders and flag them per the Legacy Flat Folders section.
## Generic Checks (when no domain structure is detected)
description: Runs dependency vulnerability audit and secrets detection
severity-default: critical
tools: [Bash, Read]
---
Run dependency audit and secrets scan to detect known CVEs in dependencies and leaked secrets in code.
## Steps
1. Check which tools are available:
```bash
pnpm --version
gitleaks version
```
- If **neither** is installed, skip this check and report: "Skipped: neither pnpm nor gitleaks installed. Install pnpm: `npm i -g pnpm`. Install gitleaks: `brew install gitleaks` or see https://github.com/gitleaks/gitleaks#installing"
- If only one is available, run that one and note the other was skipped.
2. **Dependency audit** (if pnpm is available):
```bash
pnpm audit --json 2>/dev/null || true
```
Parse the JSON output. Map advisory severity:
- `critical` advisory → `critical`
- `high` advisory → `major`
- `moderate` advisory → `minor`
- `low` advisory → `nitpick`
Report each finding with: package name, version, advisory title, CVE, and suggested patched version.
3. **Secrets detection** (if gitleaks is available):
description: Reviews whether code changes are reflected in documentation
severity-default: medium
tools: [Read, Grep]
---
You are a documentation freshness reviewer. Your job is to check whether code changes are properly reflected in documentation, and whether new features need documentation.
Check for:
1.**Stale README sections** - code changes that invalidate setup instructions, API examples, or architecture descriptions in README.md
2.**Outdated code comments** - comments referencing removed functions, old parameter names, previous behavior, or TODO items that are now done
3.**Missing JSDoc on public APIs** - exported functions, classes, or interfaces without JSDoc descriptions, especially those used by consumers of the library
4.**Changed behavior without changelog** - user-facing behavior changes that should be noted in a changelog or release notes
5.**Dead documentation links** - links in markdown files pointing to moved or deleted files
6.**Missing migration guidance** - breaking changes without upgrade instructions
Rules:
- Focus on documentation that needs to CHANGE due to the diff — don't audit all existing docs
- Do NOT flag missing comments on internal/private functions
- Do NOT flag missing changelog entries for purely internal refactors
- "Major" for stale docs that will mislead users, "minor" for missing JSDoc on public APIs, "nitpick" for minor doc improvements
## ComfyUI_frontend Documentation
This repository's public APIs are used by custom node and extension authors. Documentation lives at [docs.comfy.org](https://docs.comfy.org) (repo: Comfy-Org/docs).
For any NEW API, event, hook, or configuration that extensions or custom nodes can use:
- Flag with a suggestion to open a PR to Comfy-Org/docs to document the new API
- Example: "This new extension API should be documented at docs.comfy.org — consider opening a PR to Comfy-Org/docs"
For changes to existing extension-facing APIs:
- Check if the existing docs at docs.comfy.org may need updating
- Flag stale references in CONTRIBUTING.md or developer guides
Anything relevant to custom extension authors should trigger a documentation suggestion.
description: Validates import rules, detects circular dependencies, and enforces layer boundaries using dependency-cruiser
severity-default: high
tools: [Bash, Read]
---
Run dependency-cruiser import graph analysis on changed files to detect circular dependencies, orphan modules, and import rule violations.
> **Note:** The circular dependency scan in step 4 targets `src/` specifically, since this is a frontend app with source code under `src/`.
## Steps
1. Check if dependency-cruiser is available:
```bash
pnpm dlx dependency-cruiser --version
```
If not available, skip this check and report: "Skipped: dependency-cruiser not available. Install with: `pnpm add -D dependency-cruiser`"
> **Install:** `pnpm add -D dependency-cruiser`
2. Identify changed directories from the diff.
3. Determine config to use:
- If `.dependency-cruiser.js` or `.dependency-cruiser.cjs` exists in the repo root, use it (dependency-cruiser auto-detects it). This config may enforce layer architecture rules (e.g., base → platform → workbench → renderer import direction):
7. Report each violation with: the rule name, source and target modules, file path, and a suggestion (usually move the import or extract an interface).
description: Scans for memory leak patterns including event listeners without cleanup, timers not cleared, and unbounded caches
severity-default: high
tools: [Read, Grep]
---
You are a memory leak specialist reviewing a code diff. Focus exclusively on patterns that cause memory to grow unboundedly over time.
Check for:
1.**Event listeners without cleanup** - addEventListener without corresponding removeEventListener, especially in Vue onMounted without onBeforeUnmount cleanup
2.**Timers not cleared** - setInterval/setTimeout started in component lifecycle without clearInterval/clearTimeout on unmount
3.**Observer patterns without disconnect** - MutationObserver, IntersectionObserver, ResizeObserver created without .disconnect() on cleanup
4.**WebSocket/Worker connections** - opened connections never closed on component unmount or route change
5.**Unbounded caches** - Maps, Sets, or arrays that grow with usage but never evict entries, especially keyed by user input or dynamic IDs
6.**Stale closures holding references** - closures in event handlers or callbacks that capture large objects or DOM nodes and prevent garbage collection
7.**RequestAnimationFrame without cancel** - rAF loops started without cancelAnimationFrame on cleanup
8.**Vue-specific leaks** - watch/watchEffect without stop(), computed that captures reactive dependencies it shouldn't, provide/inject holding stale references
9.**Global state accumulation** - pushing to global arrays/maps without ever removing entries, console.log keeping object references in dev
Rules:
- Focus on NEW leak patterns introduced in the diff
- Do NOT flag existing cleanup patterns that are correct
- Every finding must explain the specific lifecycle scenario where the leak occurs (e.g., "when user navigates away from this view, the interval keeps running")
- "Critical" for leaks in hot paths or long-lived pages, "major" for component-level leaks, "minor" for dev-only or cold-path leaks
- New components must use `<script setup lang="ts">` with reactive props destructuring (Vue 3.5 style): `const { color = 'blue' } = defineProps<Props>()`
- Separate type imports from value imports
- All user-facing strings must use `vue-i18n` (`$t()` in templates, `t()` in script). Flag hardcoded English strings in templates that should be translated. The locale file is `src/locales/en/main.json`
### Tailwind (if applicable)
- No `dark:` variants (use semantic theme tokens)
- Use `cn()` utility for conditional classes
- No `!important` in utility classes
- Tailwind 4: CSS variable references use parentheses syntax: `h-(--my-var)` NOT `h-[--my-var]`
- Use design tokens: `bg-secondary-background`, `text-muted-foreground`, `border-border-default`
- No `<style>` blocks in Vue SFCs — use inline Tailwind only
### Testing
- Behavioral tests, not change detectors
- No mock-heavy tests that don't test real behavior
- Test names describe behavior, not implementation
### General
- No commented-out code
- No `console.log` in production code (unless intentional logging)
- No hardcoded URLs, credentials, or environment-specific values
- Package manager is `pnpm`. Never use `npm`, `npx`, or `yarn`. Use `pnpm dlx` for one-off package execution
- Sanitize HTML with `DOMPurify.sanitize()`, never raw `innerHTML` or `v-html` without it
Rules:
- Only flag ACTUAL violations, not hypothetical ones
- AGENTS.md conventions take priority over default patterns if they conflict
7.**Data structures** - using arrays for lookups instead of maps/sets, unnecessary copying of large objects
8.**Async patterns** - sequential awaits that could be parallel, missing abort controllers
Rules:
- ONLY report actual performance issues, not premature optimization suggestions
- Distinguish between hot paths (major) and cold paths (minor)
- Include Big-O analysis when relevant
- Do NOT suggest micro-optimizations that a JIT compiler handles
- Quantify the impact when possible: "This is O(n²) where n = number of users"
## Repo-Specific Performance Concerns
- **LiteGraph canvas rendering** is the primary hot path. Operations inside `LGraphNode.onDrawForeground`, `onDrawBackground`, `processMouseMove` run every frame at 60fps. Any O(n) or worse operation here on the node/link collections is critical.
- **Node definition lookups** happen frequently — these should use Maps, not array.find()
- **Workflow serialization/deserialization** can involve large JSON objects (1000+ nodes). Watch for deep copies or unnecessary re-parsing.
- **Vue reactivity in canvas code** — reactive getters triggered during canvas render cause performance issues. Canvas-facing code should read raw values, not reactive refs.
- **Bundle size** — check for large imports that could be dynamically imported. The build uses Vite with `build:analyze` for bundle visualization.
description: Detects potential regressions by analyzing git blame history of modified lines
severity-default: high
tools: [Bash, Read, Grep]
---
Perform regression risk analysis on the current changes using git blame.
## Method
1. Determine the base branch by examining git context (e.g., `git merge-base origin/main HEAD`, or check the PR's target branch). Never use `HEAD~1` as the base — it compares against the PR's own prior commit and causes false positives.
2. Get the PR's own commits: `git log --format=%H <base>..HEAD`
3. For each changed file, run: `git diff <base>...HEAD -- <file>`
4. Extract the modified line ranges from the diff (lines removed or changed in the base version).
5. For each modified line range, check git blame in the base version:
`git blame <base> -L <start>,<end> -- <file>`
6. Look for blame commits whose messages match bugfix patterns:
7.**Filter out false positives.** If the blamed commit SHA is in the PR's own commits, skip it.
8. For each verified bugfix line being modified, report as a finding.
## What to Report
For each finding, include:
- The file and line number
- The original bugfix commit (short SHA and subject)
- The date of the original fix
- A suggestion to verify the original bug scenario still works and to add a regression test if one doesn't exist
## Shallow Clone Limitations
When working with shallow clones, `git blame` may not have full history. If blame fails with "no such path in revision" or shows truncated history, report only findings where blame succeeds and note the limitation.
description: Runs SonarQube-grade static analysis using eslint-plugin-sonarjs
severity-default: high
tools: [Bash, Read]
---
Run eslint-plugin-sonarjs analysis on changed files to detect bugs, code smells, and security patterns without needing a SonarQube server.
## Steps
1. Check if eslint is available:
```bash
pnpm dlx eslint --version
```
If pnpm dlx or eslint is unavailable, skip this check and report: "Skipped: eslint not available. Ensure Node.js and pnpm dlx are installed."
2. Identify changed files (`.ts`, `.js`, `.vue`) from the diff.
3. Determine eslint config to use. This check uses a **strict sonarjs-specific config** (not the project's own eslint config, which is less strict):
- Look for the colocated strict config at `.agents/checks/eslint.strict.config.js`
- If found, run with `--config .agents/checks/eslint.strict.config.js`
- **Fallback:** if the strict config cannot be found or fails to load, skip this check and report: "Skipped: .agents/checks/eslint.strict.config.js missing; SonarJS rules require explicit config."
7.**Test readability** - unclear test names, complex setup that obscures intent, shared mutable state between tests
8.**Test isolation** - tests depending on execution order, shared state, external services without mocking
Rules:
- Focus on test quality and coverage gaps, not production code bugs
- "Major" for missing tests on critical logic, "minor" for missing edge case tests
- A change that adds no tests is only an issue if the change adds behavior
- Refactors without behavior changes don't need new tests
- Prefer behavioral tests: test inputs and outputs, not internal implementation
- This repo uses **colocated tests**: `.test.ts` files live next to their source files (e.g., `MyComponent.test.ts` beside `MyComponent.vue`). When checking for missing tests, look for a colocated `.test.ts` file, not a separate `tests/` directory
## Repo-Specific Testing Conventions
- Tests use **Vitest** (not Jest) — run with `pnpm test:unit`
- Test files are **colocated**: `MyComponent.test.ts` next to `MyComponent.vue`
- Use `@vue/test-utils` for component testing, `@pinia/testing` (`createTestingPinia`) for store tests
- Browser/E2E tests use **Playwright** in `browser_tests/` — run with `pnpm test:browser:local`
- Mock composables using the singleton factory pattern inside `vi.mock()` — see `docs/testing/unit-testing.md` for the pattern
- Never use `any` in test code either — proper typing applies to tests too
description: Reviews Vue 3.5+ code for framework-specific anti-patterns
severity-default: medium
tools: [Read, Grep]
---
You are a Vue 3.5 framework specialist reviewing a code diff. Focus on Vue-specific patterns, anti-patterns, and missed framework features.
Check for:
1.**Options API in new files** - new .vue files using Options API instead of Composition API with `<script setup>`. Modifications to existing Options API files are fine.
2.**Reactivity anti-patterns** - destructuring reactive objects losing reactivity, using `ref()` for objects that should be `reactive()`, accessing `.value` inside templates, incorrectly using `toRefs`/`toRef`
3.**Watch/watchEffect cleanup** - watchers without cleanup functions when they set up side effects (timers, listeners, subscriptions)
4.**Flush timing issues** - DOM access in watch callbacks without `{ flush: 'post' }`, `nextTick` misuse, accessing template refs before mount
5.**defineEmits typing** - using array syntax `defineEmits(['event'])` instead of TypeScript syntax `defineEmits<{...}>()`
6.**defineExpose misuse** - exposing internal state via `defineExpose` when events would be more appropriate (expose is for imperative methods: validate, focus, open)
7.**Prop drilling** - passing props through 3+ component levels where provide/inject would be cleaner
8.**VueUse opportunities** - manual implementations of common composables that VueUse already provides (useLocalStorage, useEventListener, useDebounceFn, useIntersectionObserver, etc.)
9.**Computed vs method** - methods used in templates for derived state that should be computed properties, or computed properties that have side effects
10.**PrimeVue usage in new code** - New components must NOT use PrimeVue. This project is migrating to shadcn-vue (Reka UI primitives). If new code imports from `primevue/*`, flag it and suggest the shadcn-vue equivalent.
Available shadcn-vue replacements in `src/components/ui/`:
For Reka UI primitives not yet wrapped, create a new component in `src/components/ui/` following the pattern in existing components (see `src/components/ui/AGENTS.md`): use `useForwardProps`, `cn()`, design tokens.
Modifications to existing PrimeVue-based components are acceptable but should note the migration opportunity.
Rules:
- Only review .vue and composable .ts files — skip stores, services, utils
- Do NOT flag existing Options API files being modified (only flag NEW files)
- Flag new PrimeVue imports — the project is migrating to shadcn-vue/Reka UI
- When suggesting shadcn-vue alternatives, reference `src/components/ui/AGENTS.md` for the component creation pattern
- Use Iconify icons (`<i class="icon-[lucide--check]" />`) not PrimeIcons
- "Major" for reactivity bugs and flush timing, "minor" for API style and VueUse opportunities, "nitpick" for preference-level patterns
description: 'Creates a PR to regenerate Playwright screenshot expectations. Use when screenshot tests are failing on main or PRs due to stale golden images. Triggers on: regen screenshots, regenerate screenshots, update expectations, fix screenshot tests.'
---
# Regenerating Playwright Screenshot Expectations
Automates the process of triggering the `PR: Update Playwright Expectations`
GitHub Action by creating a labeled PR from `origin/main`.
## Steps
1.**Fetch latest main**
```bash
git fetch origin main
```
2. **Create a timestamped branch** from `origin/main`
Format: `regen-screenshots/YYYY-MM-DDTHH` (hour resolution, local time)
@@ -13,3 +13,11 @@ This automated review performs comprehensive analysis:
- Integration concerns
For implementation details, see `.claude/commands/comprehensive-pr-review.md`.
## GitHub Actions: Fork PR Permissions
Fork PRs get a **read-only `GITHUB_TOKEN`** — no PR comments, no secret access, no pushing.
Any workflow that needs write access must use the **two-workflow split**: a `pull_request`-triggered `ci-*.yaml` uploads artifacts (including PR metadata), then a `workflow_run`-triggered `pr-*.yaml` downloads them and posts comments with write permissions. See `ci-size-data` → `pr-size-report` or `ci-perf-report` → `pr-perf-report`. Use `.github/actions/post-pr-report-comment` for the comment step.
Never write PR comments directly from `pull_request` workflows or use `pull_request_target` to run untrusted code.
--onlyAllow 'MIT;MIT*;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD;BlueOak-1.0.0;Python-2.0;CC0-1.0;Unlicense;(MIT OR Apache-2.0);(MIT OR GPL-3.0);(Apache-2.0 OR MIT);(MPL-2.0 OR Apache-2.0);CC-BY-4.0;CC-BY-3.0;GPL-3.0-only'; then
echo ''
echo '✅ All production dependency licenses are approved!'
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.