Compare commits

...

168 Commits

Author SHA1 Message Date
ComfyUI Wiki
7e0890aa3a Temporarily remove VRAM sort option
Comment out the VRAM Usage (Low to High) sort option in WorkflowTemplateSelectorDialog.vue and add a TODO, since real VRAM usage data is not available. Keeps the code in-place (commented) so it can be re-enabled when a reliable VRAM metric is implemented.
2026-02-22 10:47:35 +08:00
Alexander Brown
f9317e7078 fix: refresh deps and clear production audit vulnerabilities (#9068)
## Summary
- refresh workspace dependency catalog and lockfile for security and
maintenance updates
- resolve production audit findings (runtime dependencies now report
clean)
- align Tiptap dependencies on v2 and pin `@tiptap/pm` to avoid
mixed-version type issues
- update Storybook preview toolbar config for Storybook 10 typing
(`showName` removed)

## Validation
- `pnpm typecheck` 
- `pnpm lint`  (warnings only)
- `pnpm test:unit` 
- `pnpm audit --prod` 
- `pnpm audit` ⚠️ remaining dev-only transitive advisories
(`minimatch`/`ajv`/`brace-expansion`) in upstream toolchain deps

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9068-fix-refresh-deps-and-clear-production-audit-vulnerabilities-30e6d73d36508122b778ec3ca99d41a4)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-02-21 17:44:33 -08:00
Comfy Org PR Bot
79e71a5761 1.41.1 (#9069)
Patch version increment to 1.41.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9069-1-41-1-30f6d73d365081148845cb3024b7987f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-21 17:19:38 -08:00
Christian Byrne
3e4d273832 fix: update imagePreview browser tests to use current fixture APIs (#8995)
The browser tests added in #8143 were failing on main because they were
written against stale `ComfyPage` APIs that were refactored in #8510
(merged Feb 3, before #8143 merged Feb 18).

### Changes
- `comfyPage.dragAndDropFile` → `comfyPage.dragDrop.dragAndDropFile`
- `comfyPage.setSetting` → `comfyPage.settings.setSetting`
- `comfyPage.loadWorkflow` → `comfyPage.workflow.loadWorkflow`
- `comfyPage.getNodeRefsByType` → `comfyPage.nodeOps.getNodeRefsByType`
- Fix `comfyPage` type parameter to use `ComfyPage` import
- Remove `test.fixme` since root cause was API mismatch, not test logic

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8995-fix-update-imagePreview-browser-tests-to-use-current-fixture-APIs-30d6d73d365081219c1eda4ea7251160)
by [Unito](https://www.unito.io)
2026-02-21 16:56:25 -08:00
jaeone94
8aa4e36fd5 [refactor] Extract executionErrorStore from executionStore (#9060)
## Summary
Extracts error-related state and logic from `executionStore` into a
dedicated `executionErrorStore` for better separation of concerns.

## Changes
- **New store**: `executionErrorStore` with all error state
(`lastNodeErrors`, `lastExecutionError`, `lastPromptError`), computed
properties (`hasAnyError`, `totalErrorCount`,
`activeGraphErrorNodeIds`), and UI state (`isErrorOverlayOpen`,
`showErrorOverlay`, `dismissErrorOverlay`)
- **Moved util**: `executionIdToNodeLocatorId` extracted to
`graphTraversalUtil`, reusing `traverseSubgraphPath` and accepting
`rootGraph` as parameter
- **Updated consumers**: 12 files updated to import from
`executionErrorStore`
- **Backward compat**: Deprecated getters retained in `ComfyApp` for
extension compatibility

## Review Focus
- Deprecated getters in `app.ts` — can be removed in a future
breaking-change PR once extension authors migrate

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9060-refactor-Extract-executionErrorStore-from-executionStore-30e6d73d36508101973de835ab6b199f)
by [Unito](https://www.unito.io)
2026-02-21 16:51:22 -08:00
Christian Byrne
d9fdb01d9b fix: handle failed global subgraph blueprint loading gracefully (#9063)
## Summary

Fix "Failed to load subgraph blueprints Error: [ASSERT] Workflow content
should be loaded" error occurring on cloud.

## Changes

- **What**: `getGlobalSubgraphData` now throws on API failure instead of
returning empty string, global blueprint data is validated before
loading, and individual global blueprint errors are properly propagated
to the toast/console reporting instead of being silently swallowed.

## Review Focus

Two root causes were fixed:
1. `getGlobalSubgraphData` returned `""` on failure — this empty string
was set as `originalContent`, which is falsy, triggering the assertion
in `ComfyWorkflow.load()`.
2. `loadInstalledBlueprints` used an internal `Promise.allSettled` whose
results were discarded, so individual global blueprint failures never
reached the error reporting in `fetchSubgraphs`.

Fixes COM-15199

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9063-fix-handle-failed-global-subgraph-blueprint-loading-gracefully-30e6d73d3650818d9cc8ecf81cd0264e)
by [Unito](https://www.unito.io)
2026-02-21 16:30:01 -08:00
Alexander Brown
8f48b11f6a fix: resolve missing i18n key warnings (#9064)
## Summary

Fix multiple i18n missing key console warnings by correcting key paths
and adding missing translations.

## Changes

- **What**: 
- `ScrubableNumberInput`: Fixed references to non-existent
`g.ariaLabel.decrement`/`g.ariaLabel.increment` keys → use
`g.decrement`/`g.increment`
- `SidebarIcon`: Replaced `t()` with `st()` (safe translate) to prevent
double-translation when parent components pass pre-translated strings
- `en/main.json`: Added missing `menuLabels.Copy`, `menuLabels.Paste`,
`menuLabels.Select All` keys

## Review Focus

The `SidebarIcon` change from `t()` to `st()` is the key design
decision. `SidebarIcon` receives both i18n keys (from sidebar tabs via
`SideToolbar`) and pre-translated strings (from dedicated sidebar button
components). Using `st()` (which checks `te()` before translating)
handles both cases without warnings.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9064-fix-resolve-missing-i18n-key-warnings-30e6d73d3650816eaad3ce030f9c1d3f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-21 15:26:37 -08:00
Christian Byrne
bb40ffae3c feat: add survey registry for feature survey configurations (#8355)
## Summary
Adds a centralized registry for feature survey configurations.

## Changes
- Add `surveyRegistry.ts` with `FEATURE_SURVEYS` record for survey
configs
- Add helper functions `getSurveyConfig()` and `getEnabledSurveys()`
- Export `FeatureId` type for type-safe feature references

## Part of Nightly Survey System
This is part 3 of a stacked PR chain:
1.  feat/feature-usage-tracker - useFeatureUsageTracker (merged in
#8189)
2.  feat/survey-eligibility - useSurveyEligibility (#8189, merged)
3. **feat/survey-config** - surveyRegistry.ts (this PR)
4. feat/survey-popover - NightlySurveyPopover.vue
5. feat/survey-integration - NightlySurveyController.vue

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8355-feat-add-survey-registry-for-feature-survey-configurations-2f66d73d365081faae6bda0c14c069d9)
by [Unito](https://www.unito.io)
2026-02-21 13:38:06 -08:00
Dante
de131133bd fix: make serverFeatureFlags reactive via Vue ref (#9051) 2026-02-21 18:43:58 +09:00
Benjamin Lu
17f34788dc fix: disable inspect for non-previewable assets (#8989)
## Summary
Prevent text/other assets from opening a blank fullscreen viewer by
restricting inspect/zoom to previewable media kinds.

## Changes
- Add `isPreviewableMediaType` helper in shared `formatUtil`.
- Gate inspect/zoom actions in `AssetsSidebarTab`, `MediaAssetCard`, and
`MediaAssetContextMenu` using an allowlist (`image`, `video`, `audio`,
`3D`).
- Build gallery items from previewable assets only.
- Add unit tests for `isPreviewableMediaType`.

## Why
`ResultGallery` only renders image/video/audio; text/other assets could
previously enter fullscreen with no renderable content.

## Review Focus
- Verify text/other assets no longer show Inspect and do not open
fullscreen.
- Verify image/video/audio behavior is unchanged.
- Verify 3D still opens the 3D viewer dialog.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8989-fix-disable-inspect-for-non-previewable-assets-30c6d73d36508103a9b9da4fe50236ea)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-21 01:43:14 -08:00
Benjamin Lu
9184f9bce4 fix: support text and misc generated asset states (#8914)
## Summary
Align generated-asset state classification to a single shared source and
implement the missing text/misc states in both card and list previews.

## Changes
- **What**:
- Extended `getMediaTypeFromFilename` in
`packages/shared-frontend-utils` to return `text` and `other`, and
changed unknown/no-extension fallback from `image` to `other`.
- Added text extension handling (`txt`, `md`, `json`, `csv`, `yaml/yml`,
`xml`, `log`) and kept existing media kinds.
- Updated generated-assets UI to use shared media-type detection
directly (removed the local generated-assets classifier).
  - Added text and misc card preview components:
    - `text` -> `icon-[lucide--text]`
    - `other` -> `icon-[lucide--check-check]`
- Updated list-item preview behavior so only `image`/`video` use preview
media URLs; `text`/`other` use icon fallback.
- Widened media kind schema for asset display metadata to include `text`
and `other`.
- **Breaking**: No API breaking changes; internal media kind union
widened for frontend asset display paths.
- **Dependencies**: None.

## Review Focus
- Verify generated text assets render paragraph/text icon state in card
+ list.
- Verify unknown/misc assets consistently render double-check icon state
in card + list.
- Verify existing image/video/audio/3D behavior remains unchanged.

## Screenshots (if applicable)
<img width="282" height="158" alt="image"
src="https://github.com/user-attachments/assets/76cf2d1b-9d34-4c7c-92a1-50bbc55871e5"
/>
<img width="432" height="489" alt="image"
src="https://github.com/user-attachments/assets/024fece3-f241-484d-a37e-11948559ebbc"
/>
<img width="421" height="494" alt="image"
src="https://github.com/user-attachments/assets/ed64ba0c-bf46-4c3b-996e-4bc613ee029e"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8914-fix-support-text-and-misc-generated-asset-states-3096d73d365081f28ca7c32f306e4b50)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-21 01:27:28 -08:00
Comfy Org PR Bot
ea7bbb744f 1.41.0 (#9059)
Minor version increment to 1.41.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9059-1-41-0-30e6d73d36508103b6cbef6d402f05de)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-21 01:13:57 -08:00
Benjamin Lu
25880aa024 fix: update asset video previews for card and list (#8908)
## Summary
Render generated video previews in list items using a real video element (instead of an image element (this caused errors before)) and include a custom play button with dimming per [the designs](https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3928-39270&m=dev).

## Changes
- **What**:
  - List item preview path now renders a `video` element when `isVideoPreview` is true.
  - Video list preview uses `preload="metadata"`, `muted`, `playsinline`, and `pointer-events-none` so row click behavior stays unchanged.
  - Kept the custom overlay/play affordance and increased overlay dimming from `bg-black/10` to `bg-black/15`.
  - Updated tests for `AssetsListItem`, `MediaVideoTop`, and `AssetsSidebarListView`.

## Review Focus
- Confirm list item click behavior still opens/selects asset (no inline playback interaction).
- Confirm video list previews now show actual video frame path instead of broken image fallback.

## Limitation
Backend does not currently provide a dedicated poster/thumbnail image for video outputs in the job preview payload. In the frontend today, we can either show a video icon placeholder, or load/render the full video itself to obtain a preview frame.

## Screenshots (if applicable)
<img width="427" height="499" alt="image" src="https://github.com/user-attachments/assets/3f974817-9d73-4fee-9fa5-2f1f68942c06" />
<img width="230" height="92" alt="image" src="https://github.com/user-attachments/assets/1fbfdd6a-72dd-47e2-96bf-8f7eb41c36f2" />
2026-02-21 00:59:36 -08:00
Christian Byrne
40aa7c5974 feat(persistence): fix QuotaExceededError and cross-workspace draft leakage (#8520)
## Summary

Completes the workflow persistence overhaul by integrating the new draft
system into the app and migrating existing data. Fixes two critical
bugs:

1. **QuotaExceededError** - localStorage fills up with workflow drafts,
breaking auto-save
2. **Cross-workspace data leakage** - Drafts from one ComfyUI instance
appear in another

## Changes

- **What**: 
- `useWorkflowPersistenceV2.ts` - Main composable that hooks into graph
changes with 512ms debounce
- `migrateV1toV2.ts` - One-time migration of existing drafts to the new
scoped format
  - Updated E2E tests for new storage key patterns
- **Why**: Users lose work when storage quota is exceeded, and see
confusing workflows from other instances

## How It Works

- **Workspace scoping**: Each ComfyUI instance (identified by server
URL) has isolated draft storage
- **LRU eviction**: When storage is full, oldest drafts are
automatically removed (keeps 32 most recent)
- **Tab isolation**: Each browser tab tracks its own active/open
workflows via sessionStorage
- **Debounced saves**: Graph changes are batched with 512ms delay to
reduce storage writes

## Migration

Existing V1 drafts are automatically migrated on first load. The
migration:
1. Reads drafts from old `Comfy.Workflow.*` keys
2. Converts to new workspace-scoped format
3. Cleans up old keys after successful migration

---
*Part 4 of 4 in the workflow persistence improvements stack*

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
2026-02-21 00:57:50 -08:00
Comfy Org PR Bot
3d3a4dd1a2 1.40.10 (#9058)
Patch version increment to 1.40.10

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9058-1-40-10-30e6d73d36508133b358d9f35840055a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-21 00:57:11 -08:00
Christian Byrne
39af93ae3e feat: read category from blueprint subgraph definition (#9053)
Read `category` from `definitions.subgraphs[0].category` in blueprint
JSON files as a fallback default for node categorization.

This allows blueprint authors to set the category directly in the
blueprint file without needing backend `index.json` support. The
precedence order is:
1. Explicit overrides (e.g. `info.category` from API, or `'Subgraph
Blueprints/User'` for user blueprints)
2. `definitions.subgraphs[0].category` from the blueprint JSON content
3. Bare `'Subgraph Blueprints'` fallback

Companion PR: Comfy-Org/ComfyUI#12552 (adds essential blueprints with
categories matching the Figma design)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9053-feat-read-category-from-blueprint-subgraph-definition-30e6d73d3650810ca23bfc5a1e97cb31)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-20 23:58:15 -08:00
Dante
4849d4a6c9 fix: raise graphdialog z-index above menu bars (#9049)
## Summary

- Raise `.graphdialog` z-index from 41 to 1500 so the widget prompt
dialog is no longer hidden behind menu bars when a node is near the top
of the screen.

### Why 1500 instead of 10000?

The issue suggested 10000, but that would place the dialog **above**
context menus (9999) and at the same level as toasts (10000). A value of
1500 is sufficient to sit above the top menu bar (1001) and actionbar
(1300), while staying **below** popovers (1700), context menus (9999),
and toasts (10000).

| Element | z-index |
|---------|---------|
| `.comfyui-body-top` (top menu) | 1001 |
| Actionbar | 1300 |
| **`.graphdialog` (this PR)** | **1500** |
| Popover | 1700 |
| Context menus / search box | 9999 |
| Toast | 10000 |

- Fixes #4573
### test

#### AS IS
<img width="534" height="120" alt="스크린샷 2026-02-21 오후 1 27 56"
src="https://github.com/user-attachments/assets/e58922b4-ae3f-4083-a0e1-06c27efb64af"
/>

#### TO BE
<img width="659" height="140" alt="스크린샷 2026-02-21 오후 1 47 09"
src="https://github.com/user-attachments/assets/980334f4-b5d2-43f5-a237-d7c2a7dfb6c9"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9049-fix-raise-graphdialog-z-index-above-menu-bars-30e6d73d36508172b9b3c728c3628477)
by [Unito](https://www.unito.io)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:03:35 -08:00
Christian Byrne
55ee6e7e63 fix: "Add Subgraph to Library" context menu not saving subgraph (#9056)
## Summary

Fix "Add Subgraph to Library" context menu option which was bookmarking
(UI favorite) instead of actually saving the subgraph as a reusable
blueprint.

## Changes

- **What**: Replace `nodeBookmarkStore.addBookmark()` with
`subgraphStore.publishSubgraph()` in `addSubgraphToLibrary`, matching
the working toolbar button behavior. Hide the menu option when multiple
items are selected since `publishSubgraph` requires exactly one
SubgraphNode.

## Review Focus

The original implementation (PR #5218) used `addBookmark` which only
adds a star/favorite — it never called the `publishSubgraph` function
that serializes, prompts for a name, and saves the subgraph as a
blueprint file. The toolbar button (`SaveToSubgraphLibrary.vue`) worked
correctly because it calls the `Comfy.PublishSubgraph` command which
uses `publishSubgraph`.

The multi-select visibility guard (`!hasMultipleSelection`) matches
`SaveToSubgraphLibrary.vue`'s `v-show` guard. "Unpack Subgraph" remains
visible for multi-select since it handles multiple SubgraphNodes
correctly.

Fixes COM-15200

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9056-fix-Add-Subgraph-to-Library-context-menu-not-saving-subgraph-30e6d73d36508177ba7ef97a2fe9b893)
by [Unito](https://www.unito.io)
2026-02-20 22:56:53 -08:00
Yourz
74d285bda9 fix: resolve bookmark lookup for subgraph blueprints (#9057)
## Summary

Bookmarked subgraph blueprint nodes were not appearing in the favorites
tree because `buildBookmarkTree` looked up nodes only in
`nodeDefsByName`, which excludes subgraph blueprints.

## Changes

- **What**: Added `allNodeDefsByName` computed in `nodeDefStore` that
includes both regular nodes and subgraph blueprints. Updated
`nodeBookmarkStore.buildBookmarkTree` to use it for bookmark resolution.

## Review Focus

- `allNodeDefsByName` iterates over `nodeDefs` (which merges
`subgraphBlueprints` + `nodeDefsByName`). Confirm this doesn't introduce
performance concerns for large node sets.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9057-fix-resolve-bookmark-lookup-for-subgraph-blueprints-30e6d73d365081259bcae9d0c197de43)
by [Unito](https://www.unito.io)
2026-02-21 14:48:57 +08:00
Christian Byrne
1bcebd8293 fix: display Blueprint badge instead of UUID for global subgraph blueprints (#9048)
## Problem

The node search box badge displays long UUID strings (e.g.,
`comfyui-ltx-video-0fbc55c6-...`) for global/core blueprints, while
user-created subgraphs correctly show "Blueprint" as the badge.


![image](https://github.com/user-attachments/assets/placeholder-before.png)

## Root Cause

In `loadGlobalBlueprint`, global blueprints set `python_module:
v.info.node_pack` which contains UUID-like strings. The
`getNodeSource()` function processes `python_module` to determine badge
text, but UUID strings don't match any known pattern, resulting in ugly
badge text.

## Solution

- Change global blueprints to use `python_module: 'blueprint'` so they
display "Blueprint" badge like user blueprints
- Add `isGlobal` boolean flag to `ComfyNodeDef` schema to distinguish
global from user blueprints
- Update `isGlobalBlueprint()` to check the new `isGlobal` flag instead
of `python_module !== 'blueprint'`

## Changes

| File | Change |
|------|--------|
| `src/schemas/nodeDefSchema.ts` | Add optional `isGlobal?: boolean`
field |
| `src/stores/nodeDefStore.ts` | Add `isGlobal` field to
`ComfyNodeDefImpl` class |
| `src/stores/subgraphStore.ts` | Use `python_module: 'blueprint'` +
`isGlobal: true` for global blueprints; update `isGlobalBlueprint()`
check |

## Testing

- [x] Existing unit tests pass
- [x] TypeScript compiles without errors
- [x] Lint passes

Fixes COM-15168

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9048-fix-display-Blueprint-badge-instead-of-UUID-for-global-subgraph-blueprints-30e6d73d3650813cac27e02f8f2088df)
by [Unito](https://www.unito.io)
2026-02-20 22:34:48 -08:00
Dante
dac58ad811 feat: show template version in system info for local builds (#9018)
## Summary
<img width="985" height="417" alt="스크린샷 2026-02-21 오전 1 05 10"
src="https://github.com/user-attachments/assets/4543a5aa-d1ae-4be7-971e-7b1454625829"
/>



- Add `installed_templates_version` and `required_templates_version` to
the system stats schema
- Display the template version as a badge on the About page alongside
ComfyUI and Frontend badges
- Display the template version as a row in the local System Info table
- Highlight badge (red severity) and table row (red text) when installed
version doesn't match required version, so users can quickly spot
outdated templates

Fixes #4006

## Test plan

- [x] Open Settings > About and verify "Templates v{version}" badge
appears
- [x] Verify "Templates Version" row appears in System Info table
- [ ] When `installed_templates_version` matches
`required_templates_version`: badge is default color, table row is
default color
- [ ] When versions don't match: badge turns red, table row turns red
- [ ] When `installed_templates_version` is absent (package not
installed): badge is hidden, table row shows empty

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:21:21 +09:00
Yourz
6ee3803770 feat: implement NodeLibrarySidebarTabV2 with Reka UI components (#8548)
## Summary

Implement a redesigned Node Library sidebar using Reka UI components
with virtualized tree rendering and improved UX.

## Changes

- **What**: 
  - Add three-tab structure (Essential, All, Custom) using Reka UI Tabs
- Implement TreeExplorerV2 with virtualized tree using
TreeRoot/TreeVirtualizer for performance
  - Add node hover preview with teleport to show NodePreview component
  - Implement context menu for toggling favorites on nodes
  - Add search functionality that auto-expands matching folders
- Create panel components: EssentialNodesPanel, AllNodesPanel,
CustomNodesPanel
  - Add 'Open Manager' button in CustomNodesPanel
  - Use custom icons: comfy--node for nodes, ph--folder-fill for folders
  - New node preview component: `NodePreviewCard`
  - Api node folder icon
  - Node drag preview

- **Feature Flag**: Enabled via URL parameter `?nodeRedesign=true`

## Review Focus

- TreeExplorerV2.vue uses `[...expandedKeys]` to prevent internal
mutation by Reka UI TreeRoot
- Context menu injection key is exported from TreeExplorerV2Node.vue and
imported by TreeExplorerV2.vue
- Hover preview uses teleport to
`#node-library-node-preview-container-v2`

## Screenshots (if applicable)

| Feature | Screenshot | 
|---|---|
| All nodes tab |<img width="323" height="761" alt="image"
src="https://github.com/user-attachments/assets/1976222b-83dc-4a1b-838a-2d49aedea3b8"
/>|
| Custom nodes tab | <img width="308" height="748" alt="image"
src="https://github.com/user-attachments/assets/2c23bffb-bdaa-4c6c-8cac-7610fb7f3fb7"
/>|
|Api nodes icon | <img width="299" height="523" alt="image"
src="https://github.com/user-attachments/assets/e9ca05b0-1143-44cf-b227-6462173c7cd0"
/>|
| node preview|<img width="499" height="544" alt="image"
src="https://github.com/user-attachments/assets/8961a7b4-77ae-4e57-99cf-62d9e4e17088"
/>|
| node drag preview | <img width="434" height="289" alt="image"
src="https://github.com/user-attachments/assets/b5838c90-65d4-4bee-b2b3-c41b57870da8"
/>|



Test by adding `?nodeRedesign=true` to the URL

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8548-WIP-feat-implement-NodeLibrarySidebarTabV2-with-Reka-UI-components-2fb6d73d36508134b7e0f75a2c9b976a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
2026-02-20 22:06:09 -08:00
Jin Yi
fd2ffb7100 [feat] Replace view mode toggle with settings dropdown menu (#8950) 2026-02-21 13:49:19 +09:00
Jin Yi
c05644045f fix(assets): dismiss context menu on scroll and outside click (#8952) 2026-02-21 13:19:13 +09:00
Comfy Org PR Bot
5fe902358c 1.40.9 (#9034)
Patch version increment to 1.40.9

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9034-1-40-9-30e6d73d365081a1b1e4e7a1c0b77629)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-20 20:16:39 -08:00
Christian Byrne
c1a569211d fix: skip MatchType recalculation during graph configuration (#9004)
## Summary

Fixes COM-14955: "Bug: Switch node in subgraph causes link disconnection
on export"

## Problem

When a MatchType node (like Switch) inside a subgraph is
configured/restored, `LGraphNode.configure()` calls
`onConnectionsChange` for each input sequentially. The
`withComfyMatchType` callback was running before all links were
restored, seeing incomplete state and incorrectly computing types, which
could cause link disconnection.

## Solution

Add early return when `app.configuringGraph` is true to defer type
recalculation until after all links are restored. This pattern is
already used throughout the codebase:
- `widgetInputs.ts`
- `rerouteNode.ts`
- `customWidgets.ts`

Post-configure recomputation is handled by the existing
`requestAnimationFrame` callback in `applyMatchType`.

## Changes

- `src/core/graph/widgets/dynamicWidgets.ts` - Added 1 line: `if
(app.configuringGraph) return`
- `src/core/graph/widgets/matchTypeConfiguring.test.ts` - New test file
with 3 tests

## Testing

- All existing tests pass
- Added 3 new tests:
  - `skips type recalculation when configuringGraph is true`
  - `performs type recalculation during normal operation`
  - `connects both inputs with same type`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9004-fix-skip-MatchType-recalculation-during-graph-configuration-30d6d73d365081339088ffd8aebba107)
by [Unito](https://www.unito.io)
2026-02-20 19:44:34 -08:00
Jin Yi
2b69d7b49c [bugfix] Fix node replacements not loading due to feature flag timing (#9037)
## Summary
- Node replacements were never loaded because
`useNodeReplacementStore().load()` was called before `api.init()`,
meaning `serverFeatureFlags` was always empty at that point
- Dispatch `feature_flags` as a custom event from `api.ts` and trigger
`load()` in response within `addApiUpdateHandlers()`

## Changes
- **`api.ts`**: Dispatch `feature_flags` custom event after storing
server feature flags (already typed in `BackendApiCalls`)
- **`app.ts`**: Replace eager `load()` call with `feature_flags` event
listener inside `addApiUpdateHandlers()`, consistent with other API
event handlers
- **`nodeReplacementStore.ts`**: Use `api.getServerFeature()` directly
instead of `useFeatureFlags` composable; remove side effects from store
setup
- **`nodeReplacementStore.test.ts`**: Update mocks to match new
`api.getServerFeature` usage

## Review Focus
- Initialization ordering: listener registered before `api.init()`
ensures no missed events

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9037-bugfix-Fix-node-replacements-not-loading-due-to-feature-flag-timing-30e6d73d36508107ae2cd72e83c01e1a)
by [Unito](https://www.unito.io)
2026-02-20 19:16:01 -08:00
Christian Byrne
ee0789e153 fix: promoted widget labels show widgetName instead of "nodeId: widgetName" (#9013)
## Summary

Promoted/proxied widgets on subgraph nodes showed generic "nodeId:
widgetName" labels (e.g., "3: seed") in the LiteGraph renderer. Now they
correctly show just the widget name (e.g., "seed").

## Changes

- **What**: Set proxy widget overlay `label` to `widgetName` instead of
`name` (which contains the unique `"nodeId: widgetName"` format). The
overlay `name` still retains the unique format for internal
identification.
- Added 2 tests verifying proxy widget label defaults and user rename
behavior.

## Review Focus

- The one-line fix in `proxyWidget.ts` line 157: `label: widgetName`
instead of `label: name`
- The overlay `name` property is unchanged — only `label` (used for
display) is affected
- No code parses the label string for identification; all lookups use
`_overlay.nodeId` and `_overlay.widgetName` directly

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9013-fix-promoted-widget-labels-show-widgetName-instead-of-nodeId-widgetName-30d6d73d365081ca8d2feb68585fc187)
by [Unito](https://www.unito.io)
2026-02-20 19:14:57 -08:00
Christian Byrne
c1d07d6424 fix: restore strictExecutionOrder for Storybook Chromatic builds (#9038)
## Summary

Restore `strictExecutionOrder: true` in `.storybook/main.ts`
rolldownOptions, accidentally removed in #8834 as a merge artifact.

## Problem

Chromatic visual regression tests fail on `version-bump-*` release
branches with:
```
Error: __STORYBOOK_MODULE_CORE_EVENTS_PREVIEW_ERRORS__ is not defined
```

Rolldown without `strictExecutionOrder` doesn't guarantee module
execution order. Storybook's internal module system defines
`__STORYBOOK_MODULE_*` globals during initialization — without strict
ordering, downstream code references them before they're defined.

Only `version-bump-*` branches are affected because the
`chromatic-deployment` CI job (which actually loads and extracts stories
at runtime) is gated to those branches.

## Changes

- Restore `strictExecutionOrder: true` in `.storybook/main.ts`
rolldownOptions output config

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9038-fix-restore-strictExecutionOrder-for-Storybook-Chromatic-builds-30e6d73d365081489c53cea131b97a2f)
by [Unito](https://www.unito.io)
2026-02-20 19:03:06 -08:00
Jin Yi
4e9e3a0c26 [bugfix] Fix workspace settings member layout alignment (#9008) 2026-02-21 11:29:21 +09:00
Terry Jia
f7a83f6dfa feat: add gradient-slider widget for FLOAT inputs (#8992)
## Summary
Add a new 'gradient-slider' display mode for FLOAT widget inputs. Nodes
can specify gradient_stops (color stop arrays) to render a colored
gradient track behind the slider thumb, useful for color adjustment
parameters like hue, saturation, brightness, etc.

- GradientSlider.vue: reusable Reka UI-based gradient slider component
- GradientSliderWidget.ts: litegraph canvas-mode fallback rendering
- WidgetInputNumberGradientSlider.vue: Vue node widget integration
- Schema, registry, and type updates for gradient-slider support

this is prerequisite for color correct and balance

BE changes https://github.com/Comfy-Org/ComfyUI/pull/12536

## Screenshots (if applicable)

<img width="610" height="237" alt="image"
src="https://github.com/user-attachments/assets/b0577ca8-8576-4062-8f14-0a3612e56242"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8992-feat-add-gradient-slider-widget-for-FLOAT-inputs-30d6d73d36508199b3e8db6a0c213ab4)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-20 20:51:10 -05:00
Christian Byrne
4103379901 fix: intercept window.open in zendesk test to avoid external network dependency (#9035)
The zendesk support test was flaky because it waited for `networkidle`
on an external Zendesk page (`support.comfy.org`). External network
requests in CI are unreliable — they can timeout, be slow, or redirect.

**Fix:** Intercept `window.open` to capture the URL that would be
opened, without actually navigating to the external page. This makes the
test deterministic and fast.

- Fixes flaky test: `[chromium] ›
browser_tests/tests/dialog.spec.ts:339:3 › Support › Should open
external zendesk link with OSS tag`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9035-fix-intercept-window-open-in-zendesk-test-to-avoid-external-network-dependency-30e6d73d365081c2a021edd816b8c1d0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-20 17:28:40 -08:00
Christian Byrne
337e0486ea feat: client-side distribution filtering for blueprint subgraphs (#8686)
Adds client-side filtering of blueprint subgraphs by distribution.

**Changes:**
- Added `includeOnDistributions` typed field to `GlobalSubgraphData` in
`api.ts`
- Distribution detection: `isCloud → 'cloud'`, `isDesktop → 'desktop'`,
else `'localhost'`
- Filters subgraphs before loading — excluded blueprints are never
fetched

**Depends on:** Comfy-Org/workflow_templates schema update (merge first)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8686-feat-client-side-distribution-filtering-for-blueprint-subgraphs-2ff6d73d365081d29f79c4e3cab174ac)
by [Unito](https://www.unito.io)
2026-02-20 17:02:54 -08:00
Christian Byrne
b27eb5861a fix(queue): allow deleting failed jobs from queue progress UI (#8478)
## Summary
Failed jobs could not be removed from the Media Assets queue progress
panel because `useJobActions` only supported cancel for pending/running
jobs.

## Changes
- Add `deleteAction`, `canDeleteJob`, `runDeleteJob` to `useJobActions`
composable
- Export `removeFailedJob` from `useJobMenu` with optional task
parameter
- Update `ActiveMediaAssetCard.vue` to show delete button on failed jobs

## Testing
1. Queue a workflow that will fail (e.g., missing model)
2. Open Media Assets panel
3. Hover over the failed job card → delete button (circle-minus icon)
appears
4. Click delete → job is removed from queue


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8478-fix-queue-allow-deleting-failed-jobs-from-queue-progress-UI-2f86d73d3650810ba3aaf6cf38703bf5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-20 16:49:36 -08:00
Benjamin Lu
b3aed9afd0 feat: split job history into a dedicated sidebar tab (#8957)
## Summary

Move queue job history into a dedicated sidebar tab (gated by `Comfy.Queue.QPOV2`) and remove mixed job-history UI from the Assets sidebar so assets and job controls are separated.

## Changes

- **What**:
  - Added `JobHistorySidebarTab` with reusable job UI primitives: `JobFilterTabs`, `JobFilterActions`, `JobAssetsList`, and shared `JobHistoryActionsMenu`.
  - Added reactive `job-history` tab registration in `sidebarTabStore`; prepends above Assets when `Comfy.Queue.QPOV2` is enabled and unregisters cleanly when disabled.
  - Added debounced search to `useJobList` (filters by job title, metadata, and prompt id).
  - Extracted clear-history dialog logic to `useQueueClearHistoryDialog` and reused it from queue overlay and job history tab.
  - Removed active-job rendering and queue-clear controls from assets list/grid/tab views; assets sidebar now focuses on media assets only.
  - Removed the QPOV2 gate from `MediaAssetViewModeToggle` and updated queue/job localized copy.
  - Added and updated tests for queue overlay header actions, job filters, search filtering, sidebar tab registration, and assets sidebar behavior.

## Review Focus

- Verify QPOV2 toggle behavior:
  - `Docked Job History` menu action toggles `Comfy.Queue.QPOV2`.
  - `job-history` tab insertion/removal order and active-tab reset on removal.
- Verify behavior split between tabs:
  - Job controls (cancel/delete/view/filter/search/clear history/clear queue) live in Job History.
  - Assets sidebar loading/empty states and list/grid rendering remain correct after removing active jobs.

## Screenshots (if applicable)
<img width="670" height="707" alt="image" src="https://github.com/user-attachments/assets/3a201fcb-d104-4e95-b5fe-49c4006a30a5" />
2026-02-20 16:42:41 -08:00
AustinMroz
7baa14af86 Fix badges to bottom of node (#9033)
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/75171909-829e-49c0-9d94-3d5588f28f05"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/1ee438c7-8231-4d8f-abea-f901a682dfa3"/>|

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9033-Fix-badges-to-bottom-of-node-30d6d73d365081a584b1ca925980e59a)
by [Unito](https://www.unito.io)
2026-02-20 15:49:15 -08:00
Benjamin Lu
7921c38db9 chore: trigger snapshot refresh (#9027)
## Summary
- apply a tiny docs-only wording update in `README.md`
- create a non-functional diff to trigger snapshot regeneration workflow

## Testing
- pnpm typecheck
- pnpm lint

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9027-chore-trigger-snapshot-refresh-30d6d73d365081038bc2ed6e463d0e5f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-20 14:48:25 -08:00
jaeone94
46c40c755e feat: node-specific error tab with selection-aware grouping and error overlay (#8956)
## Summary
Enhances the error panel with node-specific views: single-node selection
shows errors grouped by message in compact mode, container nodes
(subgraph/group) expose child errors via a badge and "See Error" button,
and a floating ErrorOverlay appears after execution failure with a
deduplicated summary and quick navigation to the errors tab.

## Changes
- **Consolidate error tab**: Remove `TabError.vue`; merge all error
display into `TabErrors.vue` and drop the separate `error` tab type from
`rightSidePanelStore`
- **Selection-aware grouping**: Single-node selection regroups errors by
message (not `class_type`) and renders `ErrorNodeCard` in compact mode
- **Container node support**: Detect child-node errors in subgraph/group
nodes via execution ID prefix matching; show error badge and "See Error"
button in `SectionWidgets`
- **ErrorOverlay**: New floating card shown after execution failure with
deduplicated error messages, "Dismiss" and "See Errors" actions;
`isErrorOverlayOpen` / `showErrorOverlay` / `dismissErrorOverlay` added
to `executionStore`
- **Refactor**: Centralize error ID collection in `executionStore`
(`allErrorExecutionIds`, `hasInternalErrorForNode`); split `errorGroups`
into `allErrorGroups` (unfiltered) and `tabErrorGroups`
(selection-filtered); move `ErrorOverlay` business logic into
`useErrorGroups`

## Review Focus
- `useErrorGroups.ts`: split into `allErrorGroups` / `tabErrorGroups`
and the new `filterBySelection` parameter flow
- `executionStore.ts`: `hasInternalErrorForNode` helper and
`allErrorExecutionIds` computed
- `ErrorOverlay.vue`: integration with `executionStore` overlay state
and `useErrorGroups`

## Screenshots
<img width="853" height="461" alt="image"
src="https://github.com/user-attachments/assets/a49ab620-4209-4ae7-b547-fba13da0c633"
/>
<img width="854" height="203" alt="image"
src="https://github.com/user-attachments/assets/c119da54-cd78-4e7a-8b7a-456cfd348f1d"
/>
<img width="497" height="361" alt="image"
src="https://github.com/user-attachments/assets/74b16161-cf45-454b-ae60-24922fe36931"
/>

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-20 12:14:52 -08:00
Robin Huang
c2452c5d20 feat: add tooltip to clear queued jobs button in expanded queue overlay (#9020)
Adds a "Clear all jobs" tooltip to the cancel queued jobs button in the
expanded job queue header, matching the tooltip pattern used elsewhere
in the queue overlay.

Helps the user understand what this button does.

## Test plan
- Open the expanded job queue modal with queued jobs
- Hover over the cancel (list-x) button next to the queued count
- Verify the "Clear all jobs" tooltip appears

<img width="399" height="267" alt="Screenshot 2026-02-20 at 11 24 36 AM"
src="https://github.com/user-attachments/assets/9c83a3e8-4905-44ee-b270-b16401e9a20c"
/>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:00:35 +00:00
Christian Byrne
7fba000d68 feat: default Vue Nodes (Nodes 2.0) to enabled for new cloud installs (#9009)
## Summary

Default Nodes 2.0 (`Comfy.VueNodes.Enabled`) to `true` for new cloud
installs (≥1.41.0).

## Changes

- **What**: Add `defaultsByInstallVersion: { '1.41.0': isCloud }` to the
`Comfy.VueNodes.Enabled` setting. Since `isCloud` is a compile-time
constant, cloud builds get `{ '1.41.0': true }` while local builds get
`{ '1.41.0': false }` (tree-shaken away).

## Review Focus

- Version threshold `1.41.0` — should this match a specific upcoming
release?
- Existing users (installed before 1.41.0) and non-cloud builds are
unaffected — they keep the `defaultValue: false` fallback.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9009-feat-default-Vue-Nodes-Nodes-2-0-to-enabled-for-new-cloud-installs-30d6d73d365081268337f7ee72c24d98)
by [Unito](https://www.unito.io)
2026-02-20 09:11:49 -08:00
AustinMroz
306fb94cf5 Linear mode arrangement tweaks (#8853)
Planning to keep updates smaller and more contained in the interest of
collaboration and velocity
- The breadcrumb hamburger menu that provides workflow options is now
displayed in linear mode
- As part of this change, the reka-ui popover component now accepts
primvevue format MenuItems
- I prefer the format I had, but this makes transitioning stuff easier.
- The simplified linear history is moved to always be horizontal and
shown beneath previews.
- The label has been removed from the "Give Feedback" button on desktop
so it does not overlap
- The full side toolbar is displayed in linear mode
  - This is temporary,  but it gets the dead code pruned out now.
- Lays some groundwork for selecting an asset from the assets panel to
also select the item in the main linear panel
- The api `promptQueued` event can now optionally include a promptIds,
which list the ids for all jobs that were queued together as part of
that batch
- Update the max for the `number of generations` field to respect the
recently updated cloud limits

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/e632679c-d727-4882-841b-09e99a2f81a4"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/a9bcd809-c314-49bd-a479-2448d1a88456"/>|

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8853-Linear-mode-arrangement-tweaks-3066d73d365081589355ef753513900b)
by [Unito](https://www.unito.io)
2026-02-20 03:17:19 -08:00
Jin Yi
7bf9d51d1d [bugfix] Show active job count in assets sidebar badge (#9015)
## Summary
Assets sidebar badge now shows total active jobs (pending + running)
instead of only pending jobs, making it consistent with the "X active
jobs" label inside the panel.

## Changes
- **What**: Changed `iconBadge` in `useAssetsSidebarTab` from
`pendingTasks.length` to `activeJobsCount` (pending + running)
- Badge still hidden when QPO V2 is disabled (legacy queue)

## Review Focus
- Whether `activeJobsCount` (pending + running) is the correct metric
for the badge

<img width="1718" height="1327" alt="스크린샷 2026-02-20 오후 7 46
40(2)"
src="https://github.com/user-attachments/assets/4d0d2bed-8be9-44d7-bd62-4dd8c075d265"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9015-bugfix-Show-active-job-count-in-assets-sidebar-badge-30d6d73d365081c287a6ce9bc0a60244)
by [Unito](https://www.unito.io)
2026-02-20 20:02:10 +09:00
Christian Byrne
01f59afff2 test: fix flaky 'Can drag node' screenshot test (#8967)
## Summary

Fix intermittently failing 'Can drag node' screenshot test that blocks
CI on main and all PR branches.

## Changes

- **What**: Add `nextFrame()` waits after switching `Comfy.UseNewMenu`
from `Top` to `Disabled` in the `beforeEach` hook. The setting change
removes the top bar, causing the canvas to resize. Without waiting, the
hardcoded drag coordinates can miss the node entirely (resulting in a
canvas pan instead of a node drag).

## Review Focus

The root cause: `setSetting('Comfy.UseNewMenu', 'Disabled')` triggers a
layout shift (top bar disappears → canvas grows vertically). Litegraph
needs 1-2 frames to process the canvas resize. The drag starts at
hardcoded screen coords `{622, 400}` which only map to the node after
the resize settles.
2026-02-20 02:37:32 -08:00
Dante
f4ca285d07 feat: add Copy, Paste, Select All commands to Edit menu (#8954)
## Summary

- Add Copy, Paste, and Select All commands to the Edit menu for
mobile/touch users and accessibility
- Menu-based copy uses LiteGraph internal clipboard; existing Ctrl+C/V
behavior is unchanged

## Changes

- `useCoreCommands.ts`: Register three new commands (`CopySelected`,
`PasteFromClipboard`, `SelectAll`)
- `coreMenuCommands.ts`: Add menu entries under Edit (between Undo/Redo
and Clear Workflow)
- `useCoreCommands.test.ts`: Add unit tests for the new commands

### AS IS
<img width="260" height="176" alt="스크린샷 2026-02-18 오후 5 44 14"
src="https://github.com/user-attachments/assets/8c9c86e1-55cc-411b-9d42-429001e04630"
/>


### TO BE
<img width="516" height="497" alt="스크린샷 2026-02-19 오후 5 07 28"
src="https://github.com/user-attachments/assets/a2047541-582f-4520-a08f-98c6e532d29f"
/>


## Test plan

- [x] Verify Copy/Paste/Select All appear in Edit menu
- [x] Select nodes → Edit > Copy → Edit > Paste → nodes duplicated
- [x] Edit > Select All → all canvas items selected
- [x] Copy with no selection → no-op (no error)
- [x] Existing Ctrl+C/V keyboard shortcuts still work

Fixes #2892

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8954-feat-add-Copy-Paste-Select-All-commands-to-Edit-menu-30b6d73d365081ec9270ed2a562eaf0b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:34:45 -08:00
sno
06732b84bb Add Mixpanel detection to telemetry tree-shaking validation (#8826)
## Summary
Enhances the existing CI telemetry scan workflow to also detect Mixpanel
code in dist files, ensuring it's properly tree-shaken from OSS builds.

## Context
- Extends existing `ci-dist-telemetry-scan.yaml` (added in PR #8354)
- Based on analysis in closed PR #6777 (split into focused PRs)
- Complements GTM detection already in place
- Part of comprehensive OSS compliance effort

## Implementation
- Adds separate Mixpanel check step with specific patterns:
  - `mixpanel.init`
  - `mixpanel.identify` 
  - `MixpanelTelemetryProvider`
  - `mp.comfy.org`
  - `mixpanel-browser`
  - `mixpanel.track(`
- Separates GTM and Mixpanel checks for clarity
- Adds `DISTRIBUTION=localhost` env var to build step
- Excludes source maps from scanning

## Related
- Supersedes part of closed PR #6777
- Complements existing GTM check from PR #8354
- Related to PR #8623 (GTM-focused, may be redundant)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8826-Add-Mixpanel-detection-to-telemetry-tree-shaking-validation-3056d73d36508153bab5f55d4bb17658)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-20 02:31:37 -08:00
Christian Byrne
c809ac5a43 refactor: extract shouldUseAssetBrowser(), fix missing inputNameForBrowser, sanitize logs (#8867)
## Summary

Extract duplicated asset-browser eligibility guard into
`shouldUseAssetBrowser()`, fix a missing parameter bug, and sanitize a
log statement.

## Changes

- **What**: 
- DRY: Extract the repeated 3-condition guard (`isCloud &&
isUsingAssetAPI && isAssetBrowserEligible`) into
`assetService.shouldUseAssetBrowser()`, used by `widgetInputs.ts` and
`useComboWidget.ts`
- Bug fix: `createAssetBrowserWidget()` in `useComboWidget.ts` was
missing the `inputNameForBrowser` parameter, which could show wrong
assets for nodes with multiple model inputs
- Security: `createAssetWidget.ts` no longer logs the full raw asset
object on validation failure
- `WidgetSelect.vue` keeps an inline guard because it has a special
`widget.type === "asset"` fallback that must stay gated behind
`isUsingAssetAPI`

## Review Focus

- `shouldUseAssetBrowser()` correctly combines the three conditions that
were previously duplicated
- `WidgetSelect.vue` preserves exact behavioral equivalence (a widget
can have `type === "asset"` when the setting is off if a user toggles
the setting after creating asset widgets)

Fixes #8744

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8867-refactor-extract-shouldUseAssetBrowser-fix-missing-inputNameForBrowser-sanitize-logs-3076d73d3650818cabdcd76a351dac31)
by [Unito](https://www.unito.io)
2026-02-20 02:23:29 -08:00
Christian Byrne
0792d26f77 fix: prevent confirm dialog buttons from being unreachable on mobile with long text (#8746)
## Summary

Fix confirm dialog buttons becoming unreachable on mobile when text
contains long unbreakable words (e.g. content-hashed filenames with 100+
characters).

<img width="1080" height="2277" alt="image"
src="https://github.com/user-attachments/assets/2f42afc9-c8ec-42aa-89d5-802dbaf788fd"
/>


## Changes

- **What**: Added `overflow-wrap: break-word` and `flex-wrap` to both
confirm dialog systems so long words break properly and buttons wrap on
narrow screens.
- `ConfirmationDialogContent.vue`: Added `overflow-wrap: break-word` to
the existing scoped style and `flex-wrap` to button row.
  - `ConfirmBody.vue`: Added `break-words` tailwind class.
  - `ConfirmFooter.vue`: Added `flex-wrap` to button section.

## Review Focus

Minimal CSS-only fix across both dialog systems (legacy
`dialogService.confirm()` and newer `showConfirmDialog()`). No
behavioral changes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8746-fix-prevent-confirm-dialog-buttons-from-being-unreachable-on-mobile-with-long-text-3016d73d36508116bf55f0dc5cd89d0b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-20 02:20:02 -08:00
Christian Byrne
03f597a496 fix: open minimap settings panel above on mobile (#8589)
## Summary

On mobile viewports, the minimap settings panel now opens above the
minimap instead of to the left, preventing it from extending off-screen
on narrow viewports.

<img width="918" height="974" alt="image"
src="https://github.com/user-attachments/assets/bd42fb38-207f-437e-86f3-65cd2eccc666"
/>

<img width="1074" height="970" alt="image"
src="https://github.com/user-attachments/assets/3fdd2109-a492-4570-a8ee-e67de171126b"
/>


## Changes

- Add mobile breakpoint detection using `useBreakpoints` from VueUse
- Use `flex-col-reverse` on mobile to position panel above the minimap
- Change margin from `mr-2` (right) to `mb-2` (bottom) on mobile

## Testing

- On desktop (≥768px width): Panel opens to the left of minimap
(unchanged)
- On mobile (<768px width): Panel opens above the minimap

## Related

- Fixes [Bug: Mobile minimap settings popover opens left instead of
up](https://www.notion.so/comfy-org/Bug-Mobile-minimap-settings-popover-opens-left-instead-of-up-2fc6d73d365081549a57c9132526edca)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Minimap now adapts layout between mobile and desktop for improved
usability.
* Panel spacing and alignment adjust automatically to better fit small
screens, improving readability and control placement.
* Responsive behavior provides a more consistent experience across
device sizes, with smoother transitions between compact (mobile) and
wide (desktop) layouts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8589-fix-open-minimap-settings-panel-above-on-mobile-2fc6d73d365081ed8125c16051865c2b)
by [Unito](https://www.unito.io)
2026-02-20 02:12:19 -08:00
Christian Byrne
473713cf02 refactor: rename internal promptId/PromptId to jobId/JobId (#8730)
## Summary

Rename all internal TypeScript usage of legacy `promptId`/`PromptId`
naming to `jobId`/`JobId` across ~38 files for consistency with the
domain model.

## Changes

- **What**: Renamed internal variable names, type aliases, function
names, class getters, interface fields, and comments from
`promptId`/`PromptId` to `jobId`/`JobId`. Wire-protocol field names
(`prompt_id` in Zod schemas and `e.detail.prompt_id` accesses) are
intentionally preserved since they match the backend API contract.

## Review Focus

- All changes are pure renames with no behavioral changes
- Wire-protocol fields (`prompt_id`) are deliberately unchanged to
maintain backend compatibility
- Test fixtures updated to use consistent `job-id` naming

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8730-refactor-rename-internal-promptId-PromptId-to-jobId-JobId-3016d73d3650813ca40ce337f7c5271a)
by [Unito](https://www.unito.io)
2026-02-20 02:10:53 -08:00
Benjamin Lu
541ad387b9 fix: show stop state for active instant run button (#8917)
Switch the Run (Instant) actionbar button into a stop-state while
instant auto-queue is actively running, so users can explicitly stop
that mode from the same control.

Figma context:
https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3381-6181&m=dev

## Screenshots (if applicable)



https://github.com/user-attachments/assets/a4aca6ab-eb0c-41a2-9f05-3af7ecf2bedd
2026-02-20 01:59:15 -08:00
Benjamin Lu
7feaefd39c fix: disable job asset actions when preview output is missing (#8700)
### Motivation
- Prevent context-menu actions from operating on stale or non-existent
assets when a completed job has no `previewOutput`, since `Inspect
asset`, `Add to current workflow`, and `Download` should be inactive in
that case.
- Also ensure the `Inspect asset` entry is disabled when the optional
inspect callback is not provided to avoid unexpected behavior.

### Description
- Added an optional `disabled?: boolean` field to the `MenuEntry` type
returned by `useJobMenu` and computed `hasPreviewAsset` to detect when
`taskRef.previewOutput` is present.
- Mark `inspect-asset`, `add-to-current`, and `download` entries as
`disabled` when the preview is missing (and also when the inspect
callback is missing for `inspect-asset`), and keep `delete` omitted when
no preview exists.
- Updated `JobContextMenu.vue` to pass `:disabled="entry.disabled"` to
the rendered `Button` and to short-circuit the action emit when an entry
is disabled.
- Expanded `useJobMenu` unit tests to assert enabled states when a
preview exists and disabled states when preview or inspect handler is
missing.

### Testing
- Ran `pnpm vitest src/composables/queue/useJobMenu.test.ts` and all
tests passed (36 tests).
- Ran `pnpm lint` and the linter reported no warnings or errors.
- Ran `pnpm typecheck` (`vue-tsc --noEmit`) and type checking completed
without errors.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_69864ed56e408330b88c3e9def1b5fb5)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8700-fix-disable-job-asset-actions-when-preview-output-is-missing-2ff6d73d365081b8b72ccadf0ae43e9d)
by [Unito](https://www.unito.io)
2026-02-20 01:40:16 -08:00
Christian Byrne
73e4ae2f70 refactor: replace raw buttons with Button component in WidgetActions (#8973)
Fixes #8889

Replaces custom-styled `<button>` elements in WidgetActions.vue with the
shared Button component for better consistency with the design system.
Uses `variant="textonly"` with `size="unset"` to match the existing
dropdown menu item styling.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8973-refactor-replace-raw-buttons-with-Button-component-in-WidgetActions-30c6d73d36508194beb2f5d810dde382)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-20 01:35:24 -08:00
Christian Byrne
f5c9c72234 test: refactor widgetUtil tests to use it.for parameterization (#8971)
Fixes #8888

Refactors repetitive test cases in `widgetUtil.test.ts` to use Vitest's
`it.for` syntax for parameterized testing. Tests that follow the same
"returns default for type" pattern are consolidated while keeping unique
test cases separate.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8971-test-refactor-widgetUtil-tests-to-use-it-for-parameterization-30c6d73d365081a48e2ecf52bb7b0f98)
by [Unito](https://www.unito.io)
2026-02-20 01:31:03 -08:00
Christian Byrne
a1c54ad7aa fix: add explicit type annotations to extension callback parameters (#8966)
Fixes #8882

Adds explicit type annotations to all extension callback parameters
(`nodeCreated`, `beforeRegisterNodeDef`, `addCustomNodeDefs`) across 14
core extension files. While the types were already inferred from the
`ComfyExtension` interface, explicit annotations improve readability and
make the code self-documenting.

## Changes

- Annotate `node` parameter as `LGraphNode` in all `nodeCreated`
callbacks
- Annotate `nodeType` as `typeof LGraphNode` and `nodeData` as
`ComfyNodeDef` in all `beforeRegisterNodeDef` callbacks
- Annotate `defs` as `Record<string, ComfyNodeDef>` in
`addCustomNodeDefs`
- Add necessary type imports where missing

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8966-fix-add-explicit-type-annotations-to-extension-callback-parameters-30b6d73d36508125b074f509aa38145f)
by [Unito](https://www.unito.io)
2026-02-20 01:26:11 -08:00
pythongosssss
6902e38e6a V2 Node Search (+ hidden Node Library changes) (#8987)
## Summary

Redesigned node search with categories

## Changes

- **What**: Adds a v2 search component, leaving the existing
implementation untouched
- It also brings onboard the incomplete node library & preview changes,
disabled and behind a hidden setting
- **Breaking**: Changes the 'default' value of the node search setting
to v2, adding v1 (legacy) as an option

## Screenshots (if applicable)




https://github.com/user-attachments/assets/2ab797df-58f0-48e8-8b20-2a1809e3735f

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8987-V2-Node-Search-hidden-Node-Library-changes-30c6d73d36508160902bcb92553f147c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Yourz <crazilou@vip.qq.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>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2026-02-20 01:10:03 -08:00
Christian Byrne
8f5cdead73 refactor: use muted-textonly variant for SectionWidgets icon buttons (#8972)
Fixes #8890

Switches the Reset All and Locate Node buttons from `variant="textonly"`
with manual text color overrides to `variant="muted-textonly"`, which
already provides the muted text color. Removes redundant
`cursor-pointer`
and `text-muted-foreground` classes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8972-refactor-use-muted-textonly-variant-for-SectionWidgets-icon-buttons-30c6d73d3650819abca7e4ca5be77f15)
by [Unito](https://www.unito.io)
2026-02-20 01:00:33 -08:00
Christian Byrne
0cfd1d8e1f refactor: remove unnecessary comments from test files (#8974)
Fixes #8705

Removes redundant code comments from test files that restate what the
code already expresses through clear naming. Focuses on comments that
label obvious sections or restate assertions, while keeping comments
that explain non-obvious behavior or workarounds.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8974-refactor-remove-unnecessary-comments-from-test-files-30c6d73d3650814a826fd78da7d0d338)
by [Unito](https://www.unito.io)
2026-02-20 00:59:44 -08:00
Christian Byrne
102149fc04 fix: restore mouse-wheel scrolling in preview-as-text outputs (#8863)
## Summary

Restore mouse-wheel scrolling for read-only preview widgets (PreviewAny
plaintext and markdown modes), broken by the focus-gated wheel capture
in #6597.

## Changes

- **What**: Remove `disabled` attribute from read-only textareas (keep
`readonly`) so they can receive focus and capture wheel events. Add
`data-capture-wheel` and `tabindex` to WidgetMarkdown display div.
- **Root cause**: `disabled` elements cannot receive focus in browsers.
The focus-gated `wheelCapturedByFocusedElement()` from #6597 always
evaluated to false for disabled textareas, forwarding all wheel events
to the canvas.

## Review Focus

- Verify that removing `disabled` while keeping `readonly` does not
allow unintended editing
- Confirm `tabindex="0"` on the markdown display div does not cause
unexpected tab-order issues
- Ensure trackpad panning over unfocused widgets (#6523) still works
correctly

Fixes COM-14812

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8863-fix-restore-mouse-wheel-scrolling-in-preview-as-text-outputs-3076d73d365081719bf5e453235bb2b5)
by [Unito](https://www.unito.io)
2026-02-20 00:55:33 -08:00
Christian Byrne
7c4486ed29 feat: automatically fit view when loading templates (#8749)
## Description

When loading a template workflow, always call `fitView()` to ensure the
template is properly framed within the viewport. This provides a better
initial viewing experience for users.

## Problem

Previously, templates with embedded viewport positions (`extra.ds`)
would load at arbitrary positions, sometimes showing a blank canvas.
Users had to navigate significantly to find the workflow content.

## Solution

Added a single condition in `loadGraphData()` to check if `openSource
=== 'template'` and force `fitView()` when true, bypassing the saved
viewport position.

This change only affects template loading - normal workflow loading
behavior remains unchanged.

## Changes

- `src/scripts/app.ts`: Added condition to always call `fitView()` when
loading templates

## Testing

- Load various templates from the template selector
- Verify the workflow is fully visible and centered on load
- Verify normal workflow loading still respects saved positions

## Related

- Fixes COM-14156
- Related to COM-10087 (Center view on workflow when loading templates -
Done)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8749-feat-automatically-fit-view-when-loading-templates-3016d73d36508167a63de2ae407de7b8)
by [Unito](https://www.unito.io)
2026-02-20 00:37:38 -08:00
Christian Byrne
116685595b feat(persistence): add draft store and tab state management (#8519)
## Summary

Adds the Pinia store for managing workflow drafts and a composable for
tracking open workflow tabs per browser tab. Uses sessionStorage for
tab-specific state to support multiple ComfyUI tabs without conflicts.

## Changes

- **What**: 
- `workflowDraftStoreV2.ts` - Pinia store wrapping the LRU cache with
save/load/remove operations
- `useWorkflowTabState.ts` - Composable for tracking active workflow
path and open tabs in sessionStorage (scoped by clientId)
- **Why**: Browser tabs need independent workflow state, but the current
system uses shared localStorage keys causing tab conflicts

## Review Focus

- Store API design in `workflowDraftStoreV2.ts`
- Session vs local storage split in `useWorkflowTabState.ts`

---
*Part 3 of 4 in the workflow persistence improvements stack*

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-19 23:53:02 -08:00
Christian Byrne
8744d3dd54 feat: add Reka UI ToggleGroup for BOOLEAN widget label_on/label_off display (#8680)
## Summary

Surfaces the `label_on` and `label_off` (aka `on`/`off`) props for
BOOLEAN widgets in the Vue implementation using a new Reka UI
ToggleGroup component.

**Before:** Labels extended outside node boundaries, causing overflow
issues.
**After:** Labels truncate with ellipsis and share space equally within
the widget.

[Screencast from 2026-02-13
16-27-49.webm](https://github.com/user-attachments/assets/912bab39-50a0-4d4e-8046-4b982e145bd9)


## Changes

### New ToggleGroup Component (`src/components/ui/toggle-group/`)
- `ToggleGroup.vue` - Wrapper around Reka UI `ToggleGroupRoot` with
variant support
- `ToggleGroupItem.vue` - Styled toggle items with size variants
(sm/default/lg)
- `toggleGroup.variants.ts` - CVA variants adapted to ComfyUI design
tokens
- `ToggleGroup.stories.ts` - Storybook stories (Default, Disabled,
Outline, Sizes, LongLabels)

### WidgetToggleSwitch Updates
- Conditionally renders `ToggleGroup` when `options.on` or `options.off`
are provided
- Keeps PrimeVue `ToggleSwitch` for implicit boolean states (no labels)
- Items use `flex-1 min-w-0 truncate` to share space and handle overflow

### i18n
- Added `widgets.boolean.true` and `widgets.boolean.false` translation
keys for fallback labels

## Implementation Details

Follows the established pattern for adding Reka UI components in this
codebase:
- Uses Reka UI primitives (`ToggleGroupRoot`, `ToggleGroupItem`)
- Adapts shadcn-vue structure with ComfyUI design tokens
- Reactive provide/inject with typed `InjectionKey` for variant context
- Styling matches existing `FormSelectButton` appearance


Fixes COM-12709

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-19 23:21:31 -08:00
Christian Byrne
6c205cbf4c fix: jobs stuck in initializing state when failing before execution_start (#8689)
## Summary

Fix jobs getting permanently stuck in "initializing" state when they
fail before the `execution_start` WebSocket event fires.

## Changes

- **What**: Added `reconcileInitializingPrompts(activeJobIds)` to
`executionStore` that removes orphaned initializing prompt IDs not
present in the active jobs set. Called from `queueStore.update()` after
fetching Running/Pending jobs, ensuring stale initializing states are
cleaned up on every queue poll.

## Review Focus

- The reconciliation delegates to the existing
`clearInitializationByPromptIds` to avoid duplicating Set-diffing logic.
- Only runs during `queueStore.update()` which is already a periodic
poll — no additional network calls.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8689-fix-jobs-stuck-in-initializing-state-when-failing-before-execution_start-2ff6d73d3650814dbeeeda71c8bb7d43)
by [Unito](https://www.unito.io)
2026-02-19 22:10:01 -08:00
Christian Byrne
351d43a95a feat(persistence): add LRU draft cache with quota management (#8518)
## Summary

Adds an LRU (Least Recently Used) cache layer and storage I/O utilities
that handle localStorage quota limits gracefully. When storage is full,
the oldest drafts are automatically evicted to make room for new ones.

## Changes

- **What**: 
- `draftCacheV2.ts` - In-memory LRU cache with configurable max entries
(default 32)
- `storageIO.ts` - Storage read/write with automatic quota management
and eviction
- **Why**: Users experience `QuotaExceededError` when localStorage fills
up with workflow drafts, breaking auto-save functionality

## Review Focus

- LRU eviction logic in `draftCacheV2.ts`
- Quota error handling and recovery in `storageIO.ts`

---
*Part 2 of 4 in the workflow persistence improvements stack*

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-19 22:04:19 -08:00
Christian Byrne
9dc6203b3d docs: document subgraph limitations with autogrow and matchtype (#8709)
## Summary

Document why autogrow and matchtype don't work inside subgraphs,
covering root cause, symptoms, workarounds, and historical context.

## Changes

- **What**: Add `src/core/graph/subgraph-dynamic-input-limitations.md`
explaining the static-vs-dynamic impedance mismatch between subgraph
slots (fixed type at creation) and matchtype/autogrow (runtime
mutations). Includes Mermaid diagrams, workarounds, and PR history. Link
added to `src/lib/litegraph/AGENTS.md` for discoverability.

## Review Focus

- Accuracy of the technical explanation — @AustinMroz authored
matchtype/autogrow, @webfiltered authored the subgraph system
- Whether the workarounds section is practical and complete
- File placement: colocated at `src/core/graph/` next to both
`subgraph/` and `widgets/dynamicWidgets.ts`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8709-docs-document-subgraph-limitations-with-autogrow-and-matchtype-3006d73d36508134a64ce8c2c49e05f1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-19 21:29:17 -08:00
Christian Byrne
18875fb5e7 fix: compact bundle size report to single-line header (#8677)
## Summary
Reduces bundle size report noise by:
- Single-line header: `## 📦 Bundle: 1.2 MB gzip 🟢 -5 kB`
- Moves detailed metrics (raw, gzip, brotli, bundle counts) into
collapsible Details section
- Category glance and per-file breakdowns remain available on expand

**Before:** Multi-line summary always visible
**After:** One-line header, details collapsed

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8677-fix-compact-bundle-size-report-to-single-line-header-2ff6d73d365081a6a1b1dce4e942bc83)
by [Unito](https://www.unito.io)
2026-02-19 21:28:00 -08:00
Christian Byrne
397af47035 fix: upgrade @lobehub/i18n-cli to fix 3D API nodes i18n (#8977)
## Summary

Upgrade `@lobehub/i18n-cli` to v1.26.1 and fix corrupted locale files
that caused 3D API nodes to break in non-English locales.

## Changes

- **What**: Upgrade `@lobehub/i18n-cli` from `^1.25.1` to `^1.26.1` —
v1.26.1 fixes numeric string keys (e.g. `"0"`, `"1"` in node outputs)
being incorrectly serialized as JSON arrays instead of objects. Fix all
11 non-English locale `nodeDefs.json` files where this corruption
already existed (23-35 entries per locale).
- **Dependencies**: `@lobehub/i18n-cli` `^1.25.1` → `^1.26.1`

## Review Focus

The locale file diffs are mechanical array→object conversions. The key
change is in `pnpm-workspace.yaml` (version bump). After this fix,
running `pnpm locale` will no longer re-corrupt numeric-keyed outputs.

Affected nodes include: `Load3D`, `Preview3D`, `MeshyImageToModelNode`,
`Hunyuan3Dv2Conditioning`, `SV3D_Conditioning`,
`ConditioningStableAudio`, `GetImageSize`, and ~30 others across all
non-English locales.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8977-fix-upgrade-lobehub-i18n-cli-to-fix-3D-API-nodes-i18n-30c6d73d365081b79f98e17e13652996)
by [Unito](https://www.unito.io)
2026-02-19 21:26:38 -08:00
Jin Yi
44733f010d [refactor] Unify small modal dialog styles with showSmallLayoutDialog (#8834)
## Summary
Extract a shared `showSmallLayoutDialog` utility and move
dialog-specific logic into composables, unifying the duplicated `pt`
configurations across small modal dialogs.

## Changes
- **`showSmallLayoutDialog`**: Added to `dialogService.ts` with a single
unified `pt` config for all small modal dialogs (missing nodes, missing
models, import failed, node conflict)
- **Composables**: Extracted 4 dialog functions from `dialogService`
into dedicated composables following the `useSettingsDialog` /
`useModelSelectorDialog` pattern:
  - `useMissingNodesDialog`
  - `useMissingModelsDialog`
  - `useImportFailedNodeDialog`
  - `useNodeConflictDialog`
- Each composable uses direct imports, synchronous `show()`, `hide()`,
and a `DIALOG_KEY` constant
- Updated all call sites (`app.ts`, `useHelpCenter`, `PackEnableToggle`,
`PackInstallButton`, `useImportFailedDetection`)

## Review Focus
- Unified `pt` config removes minor style variations between dialogs —
intentional design unification

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8834-refactor-Unify-small-modal-dialog-styles-with-showSmallLayoutDialog-3056d73d365081b6963beffc0e5943bf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-19 20:58:59 -08:00
Johnpaul Chiwetelu
fe78bc6043 chore: remove unused draftTypes.ts to fix knip (#8993)
## Summary
- Remove `src/platform/workflow/persistence/base/draftTypes.ts` which is
not imported anywhere
- Fixes `knip` reporting it as an unused file

The file was added in #8517 but nothing consumes its exports yet.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8993-chore-remove-unused-draftTypes-ts-to-fix-knip-30d6d73d36508151a9e1e936864fd311)
by [Unito](https://www.unito.io)
2026-02-19 20:57:14 -08:00
Johnpaul Chiwetelu
25696ffe03 fix: remove false 'invalid directory' errors when asset API is enabled (#8961)
## Summary
- When `Comfy.Assets.UseAssetAPI` is enabled, `getAssetModelFolders()`
only discovers folders containing assets. Empty folders (e.g.
`text_encoders`, `vae`) were falsely flagged as invalid, showing
"Invalid directory specified" on every missing model dialog.
- Removes the `directory_invalid` concept entirely — the existing
`!paths` check via `getFolderPaths()` already correctly validates all
registered directories including empty ones, making `directory_invalid`
redundant.

## Before
<img width="1841" height="954" alt="Screenshot 2026-02-18 at 21 09 55"
src="https://github.com/user-attachments/assets/09cf4f28-5175-4ff6-aa9d-916568c6d9b3"
/>


## After
<img width="1134" height="738" alt="Screenshot 2026-02-18 at 21 23 29"
src="https://github.com/user-attachments/assets/578d2fa5-3fb8-401a-beee-0fd74667f08b"
/>

## Test plan
- [ ] Enable `Comfy.Assets.UseAssetAPI` setting
- [ ] Open a template referencing models in empty folders (e.g. "Image
Editing (New)")
- [ ] Verify the missing models dialog shows download buttons instead of
"Invalid directory specified" error
- [ ] Disable `Comfy.Assets.UseAssetAPI` and verify behavior is
unchanged

Fixes #8583
2026-02-19 20:51:21 -08:00
Christian Byrne
3b5c9762a4 fix: support Firefox and Safari network error messages (#8949)
Fixes #8912

The `isNetworkError` check in `useErrorHandling.ts` only matched
Chrome/Edge's `"Failed to fetch"` message, causing Firefox and Safari
users to see raw error text instead of the user-friendly
`disconnectedFromBackend` toast.

Updated the check to use a case-insensitive regex matching all three
browser variants:
- Chrome/Edge: `Failed to fetch`
- Firefox: `NetworkError when attempting to fetch resource.`
- Safari: `Load failed`

Added parameterized tests covering all three browsers plus a negative
case ensuring non-`TypeError` errors are not misclassified.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8949-fix-support-Firefox-and-Safari-network-error-messages-30b6d73d36508185b2cbd6b5447d3795)
by [Unito](https://www.unito.io)
2026-02-19 14:22:20 -08:00
Comfy Org PR Bot
7060133ff9 1.40.8 (#8968)
Patch version increment to 1.40.8

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8968-1-40-8-30c6d73d3650817d8a59e3bd9bdeb95c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-19 02:34:05 -08:00
Christian Byrne
cc2c10745b fix: use getAuthHeader in createCustomer for API key auth support (#8983)
## Summary

Re-apply the fix from PR #8408 that was accidentally reverted by PR
#8508 — `createCustomer` must use `getAuthHeader()` (not
`getFirebaseAuthHeader()`) so API key authentication works.

## Changes

- **What**: Changed `createCustomer` in `firebaseAuthStore.ts` to use
`getAuthHeader()` which falls back through workspace token → Firebase
token → API key. Added regression tests covering API key auth, Firebase
auth, and no-auth paths.

## Review Focus

This is the same one-line fix from #8408. PR #8508 ("Feat/workspaces 6
billing") overwrote it during merge because it was branched before #8408
landed. The regression test should prevent this from happening again.

Fixes COM-15060

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8983-fix-use-getAuthHeader-in-createCustomer-for-API-key-auth-support-30c6d73d365081c2aab6d5defa5298d6)
by [Unito](https://www.unito.io)
2026-02-18 20:47:12 -08:00
Christian Byrne
8ab9a7b887 feat(persistence): add workspace-scoped storage keys and types (#8517)
## Summary

Adds the foundational types and key generation utilities for
workspace-scoped workflow draft persistence. This enables storing drafts
per-workspace to prevent data leakage between different ComfyUI
instances.

[Screencast from 2026-02-08
18-17-45.webm](https://github.com/user-attachments/assets/f16226e9-c1db-469d-a0b7-aa6af725db53)

## Changes

- **What**: Type definitions for draft storage (`DraftIndexV2`,
`DraftPayloadV2`, session pointers) and key generation utilities with
workspace/client scoping
- **Why**: The current persistence system stores all drafts globally,
causing cross-workspace data leakage when users work with multiple
ComfyUI instances

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-18 19:31:24 -08:00
Christian Byrne
2dbd7e86c3 test: mark failing Vue Nodes Image Preview browser tests as fixme (#8980)
Mark the two browser tests added in #8143 as `test.fixme` — they are
failing on main.

- `opens mask editor from image preview button`
- `shows image context menu options`

- Fixes #8143 (browser test failures)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-19 02:46:55 +00:00
Alexander Brown
faede75bb4 fix: show skeleton loading state in asset folder view (#8979)
## Description

When clicking a multi-output job to enter folder view,
`resolveOutputAssetItems` fetches job details asynchronously. During
this fetch, the panel showed "No generated files found" because there
was no loading state for the folder resolution—only the media list fetch
had one.

This replaces the empty state flash with skeleton cards that match the
asset grid layout, using the known output count from metadata to render
the correct number of placeholders.

Supersedes #8960.

### Changes

- **Add shadcn/vue `Skeleton` component**
(`src/components/ui/skeleton/Skeleton.vue`)
- **Use `useAsyncState`** from VueUse to track folder asset resolution,
providing `isLoading` automatically
- **Wire `folderLoading`** into `showLoadingState` and `showEmptyState`
computeds
- **Replace `ProgressSpinner`** with a skeleton grid that mirrors the
asset card layout
- **Use `metadata.outputCount`** to predict skeleton count; falls back
to 6

### Before / After

| Before | After |
|--------|-------|
| "No generated files found" flash | Skeleton cards matching grid layout
|

## Checklist

- [x] Code follows project conventions
- [x] No `any` types introduced
- [x] Lint and typecheck pass

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8979-fix-show-skeleton-loading-state-in-asset-folder-view-30c6d73d365081fa9809f616204ed234)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-18 18:35:36 -08:00
Alexander Brown
8099cce232 feat: bulk asset export with ZIP download (#8712)
## Summary

Adds bulk asset export with ZIP download for cloud users. When selecting
2+ assets and clicking download, the frontend now requests a server-side
ZIP export instead of triggering individual file downloads.

## Changes

### New files
- **`AssetExportProgressDialog.vue`** — HoneyToast-based progress dialog
showing per-job export status with progress percentages, error
indicators, and a manual re-download button for completed exports
- **`assetExportStore.ts`** — Pinia store that tracks export jobs,
handles `asset_export` WebSocket events for real-time progress, polls
stale exports via the task API as a fallback, and auto-triggers ZIP
download on completion

### Modified files
- **`useMediaAssetActions.ts`** — `downloadMultipleAssets` now routes to
ZIP export (via `createAssetExport`) in cloud mode when 2+ assets are
selected; single assets and OSS mode still use direct download
- **`assetService.ts`** — Added `createAssetExport()` and
`getExportDownloadUrl()` endpoints
- **`apiSchema.ts`** — Added `AssetExportWsMessage` type for the
WebSocket event
- **`api.ts`** — Wired up `asset_export` WebSocket event
- **`GraphView.vue`** — Mounted `AssetExportProgressDialog`
- **`main.json`** — Added i18n keys for export toast UI

## How it works

1. User selects multiple assets and clicks download
2. Frontend calls `POST /assets/export` with asset/job IDs
3. Backend creates a ZIP task and streams progress via `asset_export`
WebSocket events
4. `AssetExportProgressDialog` shows real-time progress
5. On completion, the ZIP is auto-downloaded via a presigned URL from
`GET /assets/exports/{name}`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8712-feat-bulk-asset-export-with-ZIP-download-3006d73d365081839ec3dd3e7b0d3b77)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-18 16:36:59 -08:00
Christian Byrne
27d4a34435 fix: sync node.imgs for legacy context menu in Vue Nodes mode (#8143)
## Summary

Fixes missing "Copy Image", "Open Image", "Save Image", and "Open in
Mask Editor" context menu options on SaveImage nodes when Vue Nodes mode
is enabled.

## Changes

- Add `syncLegacyNodeImgs` store method to sync loaded image elements to
`node.imgs`
- Call sync on image load in ImagePreview component
- Simplify mask editor handling to call composable directly

## Technical Details

- Only runs when `vueNodesMode` is enabled (no impact on legacy mode)
- Reuses already-loaded `<img>` element from Vue (no duplicate network
requests)
- Store owns the sync logic, component just hands off the element

Supersedes #7416

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8143-fix-sync-node-imgs-for-legacy-context-menu-in-Vue-Nodes-mode-2ec6d73d365081c59d42cd1722779b61)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-18 16:34:45 -08:00
Terry Jia
e1e560403e feat: reuse WidgetInputNumberInput for BoundingBox numeric inputs (#8895)
## Summary
Make WidgetInputNumberInput usable without the widget system by making
the widget prop optional and adding simple min/max/step/disabled props.

BoundingBox now uses this component instead of a separate
ScrubableNumberInput

## Screenshots (if applicable)
<img width="828" height="1393" alt="image"
src="https://github.com/user-attachments/assets/68e012cf-baae-4a53-b4f8-70917cf05554"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8895-feat-add-scrub-drag-to-adjust-to-BoundingBox-numeric-inputs-3086d73d36508194b4b5e9bc823b34d1)
by [Unito](https://www.unito.io)
2026-02-18 16:32:03 -08:00
Johnpaul Chiwetelu
aff0ebad50 fix: reload template workflows when locale changes (#8963)
## Summary
- Templates were fetched once with the initial locale and cached behind
an `isLoaded` guard. Changing language updated i18n UI strings but never
re-fetched locale-specific template data (names, descriptions) from the
server.
- Extracts core template fetching into `fetchCoreTemplates()` and adds a
`watch` on `i18n.global.locale` to re-fetch when the language changes.

## Test plan
- [ ] Open the templates panel
- [ ] Change language in settings (e.g. English -> French)
- [ ] Verify template names and descriptions update without a page
refresh
- [ ] Verify initial load still works correctly

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8963-fix-reload-template-workflows-when-locale-changes-30b6d73d36508178a2f8c2c8947b5955)
by [Unito](https://www.unito.io)
2026-02-18 15:59:37 -08:00
Christian Byrne
44dc208339 fix: app mode gets stale assets history (#8918)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8918-app-mode-fix-stale-assets-history-3096d73d36508114b81df071d7289c23)
by [Unito](https://www.unito.io)
2026-02-18 15:29:00 -08:00
Christian Byrne
388c21a88d fix: lint-format CI failing on fork PRs due to missing secret (#8948)
## Summary

Fall back to `github.token` when `PR_GH_TOKEN` secret is unavailable on
fork PRs, fixing checkout failure.

## Changes

- **What**: The `ci-lint-format` workflow uses `secrets.PR_GH_TOKEN` for
checkout. Repository secrets are not available to workflows triggered by
fork PRs, causing `Input required and not supplied: token` errors (e.g.
[run
22124451916](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/22124451916)).
The fix uses `github.token` as a fallback for fork PRs — this is always
available with read-only access, which is sufficient since the workflow
already skips commit/push for forks.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8948-fix-lint-format-CI-failing-on-fork-PRs-due-to-missing-secret-30b6d73d365081dfb78cf05362a6653c)
by [Unito](https://www.unito.io)
2026-02-18 15:28:48 -08:00
Alexander Brown
b28f46d237 Regenerate images (#8959)
```



```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8959-Regenerate-image-30b6d73d3650811e9116cb7c6c9002cb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-18 11:28:47 -08:00
Johnpaul Chiwetelu
2900e5e52e fix: asset browser filters stick when navigating categories (#8945)
## Summary

File format and base model filters in the asset browser persisted when
navigating to categories that don't contain matching assets, showing
empty results with no way to clear the filter.

## Changes

- **What**: Apply the same scope-aware filtering pattern from the
template selector dialog. Selected filters that don't exist in the
current category become inactive (excluded from filtering) but are
preserved so they reactivate when navigating back. Uses writable
computeds in `AssetFilterBar` (matching
`WorkflowTemplateSelectorDialog`) and active filter intersection in
`useAssetBrowser` (matching `useTemplateFiltering`).

## Before



https://github.com/user-attachments/assets/5c61e844-7ea0-489c-9c44-e0864dc916bc





## After


https://github.com/user-attachments/assets/8372e174-107c-41e2-b8cf-b7ef59fe741b



## Review Focus

The pattern mirrors `selectedModelObjects`/`selectedUseCaseObjects` in
`WorkflowTemplateSelectorDialog.vue` and `activeModels`/`activeUseCases`
in `useTemplateFiltering.ts`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8945-fix-asset-browser-filters-stick-when-navigating-categories-30b6d73d365081609ac5c3982a1a03fc)
by [Unito](https://www.unito.io)
2026-02-18 12:55:05 +01:00
Comfy Org PR Bot
07e64a7f44 1.40.7 (#8944)
Patch version increment to 1.40.7

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8944-1-40-7-30b6d73d365081679aa8cba674700980)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-18 02:23:34 -08:00
Benjamin Lu
34e21f3267 fix(queue): address follow-up review comments from #8740 (#8880)
## Summary
Address follow-up review feedback from #8740 by consolidating
completed-banner thumbnail fields and tightening queue count
sanitization.

## Changes
- **What**:
- Consolidated completed notification thumbnail data to `thumbnailUrls`
only by removing legacy `thumbnailUrl` support from the queue banner
notification type and renderer.
- Updated queue notification banner stories to use `thumbnailUrls`
consistently.
- Simplified `sanitizeCount` to treat any non-positive or non-number
value as the fallback count (`1`).

## Review Focus
Confirm the completed notification payload shape is now consistently
`thumbnailUrls` and no consumer relies on `thumbnailUrl`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8880-fix-queue-address-follow-up-review-comments-from-8740-3076d73d365081719a70d860691c5f05)
by [Unito](https://www.unito.io)
2026-02-17 21:26:02 -08:00
jaeone94
1349fffbce Feat/errors tab panel (#8807)
## Summary

Add a dedicated **Errors tab** to the Right Side Panel that displays
prompt-level, node validation, and runtime execution errors in a
unified, searchable, grouped view — replacing the need to rely solely on
modal dialogs for error inspection.

## Changes

- **What**:
  - **New components** (`errors/` directory):
- `TabErrors.vue` — Main error tab with search, grouping by class type,
and canvas navigation (locate node / enter subgraph).
- `ErrorNodeCard.vue` — Renders a single error card with node ID badge,
title, action buttons, and error details.
- `types.ts` — Shared type definitions (`ErrorItem`, `ErrorCardData`,
`ErrorGroup`).
- **`executionStore.ts`** — Added `PromptError` interface,
`lastPromptError` ref and `hasAnyError` computed getter. Clears
`lastPromptError` alongside existing error state on execution start and
graph clear.
- **`rightSidePanelStore.ts`** — Registered `'errors'` as a valid tab
value.
- **`app.ts`** — On prompt submission failure (`PromptExecutionError`),
stores prompt-level errors (when no node errors exist) into
`lastPromptError`. On both runtime execution error and prompt error,
deselects all nodes and opens the errors tab automatically.
- **`RightSidePanel.vue`** — Shows the `'errors'` tab (with ⚠ icon) when
errors exist and no node is selected. Routes to `TabErrors` component.
- **`TopMenuSection.vue`** — Highlights the action bar with a red border
when any error exists, using `hasAnyError`.
- **`SectionWidgets.vue`** — Detects per-node errors by matching
execution IDs to graph node IDs. Shows an error icon (⚠) and "See Error"
button that navigates to the errors tab.
- **`en/main.json`** — Added i18n keys: `errors`, `noErrors`,
`enterSubgraph`, `seeError`, `promptErrors.*`, and `errorHelp*`.
- **Testing**: 6 unit tests (`TabErrors.test.ts`) covering
prompt/node/runtime errors, search filtering, and clipboard copy.
- **Storybook**: 7 stories (`ErrorNodeCard.stories.ts`) for badge
visibility, subgraph buttons, multiple errors, runtime tracebacks, and
prompt-only errors.
- **Breaking**: None
- **Dependencies**: None — uses only existing project dependencies
(`vue-i18n`, `pinia`, `primevue`)

## Related Work

> **Note**: Upstream PR #8603 (`New bottom button and badges`)
introduced a separate `TabError.vue` (singular) that shows per-node
errors when a specific node is selected. Our `TabErrors.vue` (plural)
provides the **global error overview** — a different scope. The two tabs
coexist:
> - `'error'` (singular) → appears when a node with errors is selected →
shows only that node's errors
> - `'errors'` (plural) → appears when no node is selected and errors
exist → shows all errors grouped by class type
>
> A future consolidation of these two tabs may be desirable after design
review.

## Architecture

```
executionStore
├── lastPromptError: PromptError | null     ← NEW (prompt-level errors without node IDs)
├── lastNodeErrors: Record<string, NodeError>  (existing)
├── lastExecutionError: ExecutionError         (existing)
└── hasAnyError: ComputedRef<boolean>       ← NEW (centralized error detection)

TabErrors.vue (errors tab - global view)
├── errorGroups: ComputedRef<ErrorGroup[]>  ← normalizes all 3 error sources
├── filteredGroups                          ← search-filtered view
├── locateNode()                            ← pan canvas to node
├── enterSubgraph()                         ← navigate into subgraph
└── ErrorNodeCard.vue                       ← per-node card with copy/locate actions

types.ts
├── ErrorItem      { message, details?, isRuntimeError? }
├── ErrorCardData  { id, title, nodeId?, errors[] }
└── ErrorGroup     { title, cards[], priority }
```

## Review Focus

1. **Error normalization logic** (`TabErrors.vue` L75–150): Three
different error sources (prompt, node validation, runtime) are
normalized into a common `ErrorGroup → ErrorCardData → ErrorItem`
hierarchy. Edge cases to verify:
- Prompt errors with known vs unknown types (known types use localized
descriptions)
   - Multiple errors on the same node (grouped into one card)
   - Runtime errors with long tracebacks (capped height with scroll)

2. **Canvas navigation** (`TabErrors.vue` L210–250): The `locateNode`
and `enterSubgraph` functions navigate to potentially nested subgraphs.
The double `requestAnimationFrame` is required due to LiteGraph's
asynchronous subgraph switching — worth verifying this timing is
sufficient.

3. **Store getter consolidation**: `hasAnyError` replaces duplicated
logic in `TopMenuSection` and `RightSidePanel`. Confirm that the
reactive dependency chain works correctly (it depends on 3 separate
refs).

4. **Coexistence with upstream `TabError.vue`**: The singular `'error'`
tab (upstream, PR #8603) and our plural `'errors'` tab serve different
purposes but share similar naming. Consider whether a unified approach
is preferred.

## Test Results

```
✓ renders "no errors" state when store is empty
✓ renders prompt-level errors (Group title = error message)
✓ renders node validation errors grouped by class_type
✓ renders runtime execution errors from WebSocket
✓ filters errors based on search query
✓ calls copyToClipboard when copy button is clicked

Test Files  1 passed (1)
     Tests  6 passed (6)
```

## Screenshots (if applicable)
<img width="1238" height="1914" alt="image"
src="https://github.com/user-attachments/assets/ec39b872-cca1-4076-8795-8bc7c05dc665"
/>
<img width="669" height="1028" alt="image"
src="https://github.com/user-attachments/assets/bdcaa82a-34b0-46a5-a08f-14950c5a479b"
/>
<img width="644" height="1005" alt="image"
src="https://github.com/user-attachments/assets/ffef38c6-8f42-4c01-a0de-11709d54b638"
/>
<img width="672" height="505" alt="image"
src="https://github.com/user-attachments/assets/5cff7f57-8d79-4808-a71e-9ad05bab6e17"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8807-Feat-errors-tab-panel-3046d73d36508127981ac670a70da467)
by [Unito](https://www.unito.io)
2026-02-17 21:01:15 -08:00
Terry Jia
cde872fcf7 fix: eliminate visual shaking when adjusting crop region (#8896)
## Summary
Replace the dual-image approach (dimmed <img> + background-image in crop
box) with a single <img> and a box-shadow overlay on the crop box. The
previous approach required two independently positioned images to be
pixel-perfectly aligned, which caused visible jitter from sub-pixel
rounding differences, especially when zoomed in.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8896-fix-eliminate-visual-shaking-when-adjusting-crop-region-3086d73d365081309703d3ab0d4ad44d)
by [Unito](https://www.unito.io)
2026-02-17 16:56:46 -08:00
Terry Jia
596df0f0c6 fix: resolve ImageCrop input image through subgraph nodes (#8899)
## Summary
When ImageCrop's input comes from a subgraph node, getInputNode returns
the subgraph node itself which has no image outputs.
Use resolveSubgraphOutputLink to trace through to the actual source node
(e.g. LoadImage) inside the subgraph.

Use canvas.graph (the currently active graph/subgraph) as the primary
lookup, falling back to rootGraph. When the ImageCrop node is inside a
subgraph, rootGraph cannot find it since it only contains root-level
nodes.

## Screenshots (if applicable)
before


https://github.com/user-attachments/assets/c3995f7c-6bcd-41fe-bc41-cfd87f9be94a


after


https://github.com/user-attachments/assets/ac660f58-6e6a-46ad-a441-84c7b88d28e2

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8899-fix-resolve-ImageCrop-input-image-through-subgraph-nodes-3086d73d36508172a759d7747190591f)
by [Unito](https://www.unito.io)
2026-02-17 16:56:24 -08:00
Johnpaul Chiwetelu
d3c0e331eb fix: detect video output from data in Nodes 2.0 (#8943)
## Summary

- Fixes SaveWebM node showing "Error loading image" in Vue nodes mode
- Extracts `isAnimatedOutput`/`isVideoOutput` utility functions from
inline logic in `unsafeUpdatePreviews` so both the litegraph canvas
renderer and Vue nodes renderer can detect video output directly from
execution data
- Uses output-based detection in `imagePreviewStore.isImageOutputs` to
avoid applying image preview format conversion to video files

## Background

In Vue nodes mode, `nodeMedia` relied on `node.previewMediaType` to
determine if output is video. This property is only set via
`onDrawBackground` → `unsafeUpdatePreviews` in the litegraph canvas
path, which doesn't run in Vue nodes mode. This caused webm output to
render via `<img>` instead of `<video>`.

## Before


https://github.com/user-attachments/assets/36f8a033-0021-4351-8f82-d19e3faa80c2


## After


https://github.com/user-attachments/assets/6558d261-d70e-4968-9637-6c24532e23ac
## Test plan

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] `pnpm test:unit` passes (4500 tests)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8943-fix-detect-video-output-from-data-in-Vue-nodes-mode-30a6d73d365081e98e91d6d1dcc88785)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-17 16:17:23 -08:00
Dante
b47414a52f fix: prevent duplicate node search filters (#8935)
## Summary

- Add duplicate check in `addFilter` to prevent identical filter chips
(same `filterDef.id` and `value`) from being added to the node search
box

## Related Issue

- Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/3559

## Changes

- `NodeSearchBoxPopover.vue`: Guard `addFilter` with `isDuplicate` check
comparing `filterDef.id` and `value`
- `NodeSearchBoxPopover.test.ts`: Add unit tests covering duplicate
prevention, distinct id, and distinct value cases

## QA

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] Unit tests pass (4/4)
- [x] Bug reproduced with Playwright before fix

### as-is
<img width="719" height="269" alt="스크린샷 2026-02-17 오후 5 45 48"
src="https://github.com/user-attachments/assets/403bf53a-53dd-4257-945f-322717f304b3"
/>

### to-be
<img width="765" height="291" alt="스크린샷 2026-02-17 오후 5 44 25"
src="https://github.com/user-attachments/assets/7995b15e-d071-4955-b054-5e0ca7c5c5bf"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8935-fix-prevent-duplicate-node-search-filters-30a6d73d3650816797cfcc524228f270)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:53:08 -08:00
Simula_r
631d484901 refactor: workspaces DDD (#8921)
## Summary

Refactor: workspaces related functionality into DDD structure.

Note: this is the 1st PR of 2 more refactoring.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8921-refactor-DDD-3096d73d3650812bb7f6eb955f042663)
by [Unito](https://www.unito.io)
2026-02-17 12:28:47 -08:00
Christian Byrne
e83e396c09 feat: gate node replacement loading on server feature flag (#8750)
## Summary

Gates the node replacement store's `load()` call behind the
`node_replacements` server feature flag, so the frontend only calls
`/api/node_replacements` when the backend advertises support.

## Changes

- Added `NODE_REPLACEMENTS = 'node_replacements'` to `ServerFeatureFlag`
enum
- Added `nodeReplacementsEnabled` getter to `useFeatureFlags()`
- Added `api.serverSupportsFeature('node_replacements')` guard in
`useNodeReplacementStore.load()`

## Context

Without this guard, the frontend would attempt to fetch node
replacements from backends that don't support the endpoint, causing 404
errors.

Companion backend PR: https://github.com/Comfy-Org/ComfyUI/pull/12362

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8750-feat-gate-node-replacement-loading-on-server-feature-flag-3026d73d365081ec9246d77ad88f5bdc)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 11:39:07 -08:00
Benjamin Lu
821c1e74ff fix: use gtag get for checkout attribution (#8930)
## Summary

Replace checkout attribution GA identity sourcing from
`window.__ga_identity__` with GA4 `gtag('get', ...)` calls keyed by
remote config measurement ID.

## Changes

- **What**:
  - Add typed global `gtag` get definitions and shared GA field types.
- Fetch `client_id`, `session_id`, and `session_number` via `gtag('get',
measurementId, field, callback)` with timeout-based fallback.
- Normalize numeric GA values to strings before emitting checkout
attribution metadata.
- Update checkout attribution tests to mock `gtag` retrieval and verify
requested fields + numeric normalization.
  - Add `ga_measurement_id` to remote config typings.

## Review Focus

Validate the `gtag('get', ...)` retrieval path and failure handling
(`undefined` fallback on timeout/errors) and confirm analytics field
names match GA4 expectations.

## Screenshots (if applicable)

N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8930-fix-use-gtag-get-for-checkout-attribution-30a6d73d365081dcb773da945daceee6)
by [Unito](https://www.unito.io)
2026-02-17 02:43:34 -08:00
Comfy Org PR Bot
d06cc0819a 1.40.6 (#8927)
Patch version increment to 1.40.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8927-1-40-6-30a6d73d365081498d88d11c5f24a0ed)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2026-02-17 02:22:09 -08:00
pythongosssss
f5f5a77435 Add support for dragging in multiple workflow files at once (#8757)
## Summary

Allows users to drag in multiple files that are/have embedded workflows
and loads each of them as tabs.
Previously it would only load the first one.

## Changes

- **What**: 
- process all files from drop event
- add defered errors so you don't get errors for non-visible workflows

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8757-Add-support-for-dragging-in-multiple-workflow-files-at-once-3026d73d365081c096e9dfb18ba01253)
by [Unito](https://www.unito.io)
2026-02-16 23:45:22 -08:00
Jin Yi
efe78b799f [feat] Node replacement UI (#8604)
## Summary
Add node replacement UI to the missing nodes dialog. Users can select
and replace deprecated/missing nodes with compatible alternatives
directly from the dialog.

## Changes
- Classify missing nodes into **Replaceable** (quick fix) and **Install
Required** sections
- Add select-all checkbox + per-node checkboxes for batch replacement
- `useNodeReplacement` composable handles in-place node replacement on
the graph:
  - Simple replacement (configure+copy) for nodes without mapping
  - Input/output connection remapping for nodes with mapping
  - Widget value transfer via `old_widget_ids`
  - Dot-notation input handling for Autogrow/DynamicCombo
  - Undo/redo support via `changeTracker` (try/finally)
  - Title and properties preservation
- Footer UX: "Skip for Now" button when all nodes are replaceable (cloud
+ OSS)
- Auto-close dialog when all replaceable nodes are replaced and no
non-replaceable remain
- Settings navigation link from "Don't show again" checkbox
- 505-line unit test suite for `useNodeReplacement`

## Review Focus
- `useNodeReplacement.ts` — core graph manipulation logic
- `MissingNodesContent.vue` — checkbox selection state management
- `MissingNodesFooter.vue` — conditional button rendering (cloud vs OSS
vs all-replaceable)


[screen-capture.webm](https://github.com/user-attachments/assets/7dae891c-926c-4f26-987f-9637c4a2ca16)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8604-feat-Node-replacement-UI-2fd6d73d36508148a371dabb8f4115af)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-16 23:33:41 -08:00
Benjamin Lu
e70484d596 fix: move queue assets action into filter controls (#8926)
## Summary

Move the queue overlay "Show assets" action into the filter controls as
an icon button, so the action is available inline with other list
controls while keeping existing behavior.

## Changes

- **What**:
- Remove the full-width "Show assets" button from
`QueueOverlayExpanded`.
- Add a secondary icon button in `JobFiltersBar` with tooltip +
aria-label and emit `showAssets` on click.
- Wire `showAssets` from `JobFiltersBar` through `QueueOverlayExpanded`
to the existing handler.
- Add `JobFiltersBar` unit coverage to verify `showAssets` is emitted
when the icon button is clicked.

## Review Focus

- Verify the icon button placement in the filter row is sensible and
discoverable.
- Verify clicking the new button opens the assets panel as before.
- Verify tooltip and accessibility label copy are correct.

## Screenshots (if applicable)
Design:
https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3924-38560&m=dev
<img width="349" height="52" alt="Screenshot 2026-02-16 at 4 53 34 PM"
src="https://github.com/user-attachments/assets/347772d6-5536-457a-a65f-de251e35a0e4"
/>
2026-02-16 18:19:16 -08:00
Benjamin Lu
3dba245dd3 fix: move clear queued controls into queue header (#8920)
## Summary
- Move queued-count summary and clear-queued action into the Queue Overlay header so controls remain visible while expanded content scrolls.

## What changed
- `QueueOverlayExpanded.vue`
  - Passes `queuedCount` and `clearQueued` through to the header.
  - Removes duplicated summary/action content from the lower section.
- `QueueOverlayHeader.vue`
  - Accepts new header data/actions for queued count and clear behavior.
  - Renders queued summary and clear button beside the title.
  - Adjusts layout to support persistent header actions.
- Updated header unit tests to cover queued summary rendering and clear action behavior.

## Testing
- Header unit tests were updated for the new behavior.
- No additional test execution was requested.

## Notes
- UI composition change only; queue execution semantics are unchanged.

Design: https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3924-38560&m=dev

<img width="356" height="59" alt="Screenshot 2026-02-16 at 3 30 44 PM" src="https://github.com/user-attachments/assets/987e42bd-9e24-4e65-9158-3f96b5338199" />
2026-02-16 18:03:52 -08:00
Benjamin Lu
2ca0c30cf7 fix: localize queue overlay running and queued summary (#8919)
## Summary
- Localize QueueProgressOverlay header counts with dedicated i18n pluralization keys for running and queued jobs.
- Replace the previous aggregate active-job wording with a translated running/queued summary.

## What changed
- Updated `QueueProgressOverlay.vue` to derive `runningJobsLabel`, `queuedJobsLabel`, and `runningQueuedSummary` via `useI18n`.
- Added `QueueProgressOverlay.test.ts` coverage for expanded-header text in both active and empty queue states.
- Added new English locale keys in `src/locales/en/main.json`:
  - `runningJobsLabel`
  - `queuedJobsLabel`
  - `runningQueuedSummary`

## Testing
- `Storybook Build Status` passed.
- `Playwright Tests` were still running at the last check; merge should wait for completion.

## Notes
- Behavioral scope is limited to queue overlay header text/rendering.

Design: https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3924-38560&m=dev

<img width="356" height="59" alt="Screenshot 2026-02-16 at 3 30 44 PM" src="https://github.com/user-attachments/assets/987e42bd-9e24-4e65-9158-3f96b5338199" />
2026-02-16 17:48:47 -08:00
Benjamin Lu
c8ba5f7300 fix: add pulsing active-jobs indicator on queue button (#8915)
## Summary

Add a small pulsing blue indicator dot to the top-right of the `N
active` queue button when there are active jobs.

## Changes

- **What**: Reused `StatusBadge` (`variant="dot"`) in `TopMenuSection`
as a top-right indicator on the queue toggle button, shown only when
`activeJobsCount > 0` and animated with `animate-pulse`.
- **What**: Added tests to verify the indicator appears for nonzero
active jobs and is hidden when there are no active jobs.

## Review Focus

- Dot positioning on the queue button (`-top-0.5 -right-0.5`) across top
menu layouts.
- Indicator visibility behavior tied to `activeJobsCount > 0`.

## Screenshots (if applicable)


https://github.com/user-attachments/assets/9bdb7675-3e58-485b-abdd-446a76b2dafc

won't be shown on 0 active, I was just testing locally

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8915-fix-add-pulsing-active-jobs-indicator-on-queue-button-3096d73d36508181abf5c27662e0d9ae)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-17 01:32:33 +00:00
AustinMroz
39cc8ab97a A heavy-handed fix for middlemouse pan (#8865)
Sometimes, middle mouse clicks would fail to initiate a canvas pan,
depending on the target of the initial pan. This PR adds a capturing
event handler to the transform pane that forwards the pointer event to
canvas if
- It is a middle mouse click
- The target element is not a focused text element

Resolves #6911

While testing this, I encountered infrequent cases of "some nodes
unintentionally translating continually to the left". Reproduction was
too unreliable to properly track down, but did appear unrelated to this
PR.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8865-A-heavy-handed-fix-for-middlemouse-pan-3076d73d365081ea9a4ddd5786fc647a)
by [Unito](https://www.unito.io)
2026-02-16 15:40:36 -08:00
Alexander Brown
2ee0a1337c fix: prevent XSS vulnerability in context menu labels (#8887)
Replace innerHTML with textContent when setting context menu item labels
to prevent XSS attacks via malicious filenames. This fixes a security
vulnerability where filenames like "<img src=x onerror=alert()>" could
execute arbitrary JavaScript when displayed in dropdowns.

https://claude.ai/code/session_01LALt1HEgGvpWD7hhqcp2Gu

## Summary

<!-- One sentence describing what changed and why. -->

## Changes

- **What**: <!-- Core functionality added/modified -->
- **Breaking**: <!-- Any breaking changes (if none, remove this line)
-->
- **Dependencies**: <!-- New dependencies (if none, remove this line)
-->

## 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-8887-fix-prevent-XSS-vulnerability-in-context-menu-labels-3086d73d365081ccbe3cdb35cd7e5cb1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-16 15:31:00 -08:00
Christian Byrne
980f280b3c fix: align in-app pricing copy with comfy.org/cloud/pricing (#8725)
## Summary

Align stale in-app pricing strings and links with the current
comfy.org/cloud/pricing page.

## Changes

- **What**: Update video estimate numbers (standard 120→380, creator
211→670, pro 600→1915), fix template URL (`video_wan2_2_14B_fun_camera`
→ `video_wan2_2_14B_i2v`), fix `templateNote` to reference Wan 2.2
Image-to-Video, align `videoEstimateExplanation` wording order with
website, remove stale "$10" from `benefit1` string.

## Review Focus

Copy-only changes across 4 files — no logic or UI changes. Source of
truth: https://www.comfy.org/cloud/pricing

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8725-fix-align-in-app-pricing-copy-with-comfy-org-cloud-pricing-3006d73d3650811faf11c248e6bf27c3)
by [Unito](https://www.unito.io)
2026-02-16 11:06:20 -08:00
Comfy Org PR Bot
4856fb0802 1.40.5 (#8905)
Patch version increment to 1.40.5

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8905-1-40-5-3096d73d365081c2aecccb7ae55def79)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-02-16 10:06:15 -08:00
Christian Byrne
82ace36982 fix: show user-friendly message for network errors (#8748)
## Summary

Replaces cryptic "Failed to fetch" error messages with user-friendly
"Disconnected from backend" messages.

## Changes

- **What**: Detect network fetch errors in the central toast error
handler and display a helpful message with suggested action ("Check if
the server is running")

## Review Focus

The fix is intentionally minimal—only the central `toastErrorHandler` is
modified since all user-facing API errors flow through it.

Fixes COM-1839

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8748-fix-show-user-friendly-message-for-network-errors-3016d73d365081b3869bf19550c9af93)
by [Unito](https://www.unito.io)
2026-02-16 03:01:17 -08:00
Terry Jia
3d88d0a6ab fix: resolve errors when converting ImageCrop node to subgraph (#8898)
## Summary

- Pass callback directly to addWidget instead of null to eliminate
'addWidget without a callback or property assigned' warning
- Serialize object widget values as plain objects in
LGraphNode.serialize to prevent DataCloneError when structuredClone
encounters Vue reactive proxies

## Screenshots (if applicable)
before

https://github.com/user-attachments/assets/af20bd84-3e0e-4eca-b095-eaf4d5bb6884

after


https://github.com/user-attachments/assets/5a17772e-04bc-4f3e-abec-78c540e0efa3

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8898-fix-resolve-errors-when-converting-ImageCrop-node-to-subgraph-3086d73d365081b2ae34db31225683ad)
by [Unito](https://www.unito.io)
2026-02-16 05:46:46 -05:00
Comfy Org PR Bot
21cfd44a2d 1.40.4 (#8885)
Patch version increment to 1.40.4

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8885-1-40-4-3086d73d3650814d81c4fd16ab570538)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-15 04:15:03 -08:00
Christian Byrne
d8d0dcbf71 feat: add reset-to-default for widget parameters in right side panel (#8861)
## Summary

Add per-widget and reset-all-parameters functionality to the right side
panel, allowing users to quickly revert widget values to their defaults.

## Changes

- **What**: Per-widget "Reset to default" option in the WidgetActions
overflow menu, plus a "Reset all parameters" button in each
SectionWidgets header. Defaults are derived from the InputSpec (explicit
default, then type-specific fallbacks: 0 for INT/FLOAT, false for
BOOLEAN, empty string for STRING, first option for COMBO).
- **Dependencies**: Builds on #8594 (WidgetValueStore) for reactive UI
updates after reset.

## Review Focus

- `getWidgetDefaultValue` fallback logic in `src/utils/widgetUtil.ts` —
are the type-specific defaults appropriate?
- Deep equality check (`isEqual`) for disabling the reset button when
the value already matches the default.
- Event flow: WidgetActions emits `resetToDefault` → WidgetItem forwards
→ SectionWidgets handles via `writeWidgetValue` (sets value, triggers
callback, marks canvas dirty).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8861-feat-add-reset-to-default-for-widget-parameters-in-right-side-panel-3076d73d365081d1aa08d5b965a16cf4)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2026-02-15 00:55:04 -08:00
Christian Byrne
066a1f1f11 fix: drag-and-drop screenshot creates LoadImage node instead of showing error (#8886)
## Summary

Fix drag-and-drop of local screenshots onto the canvas failing to create
a LoadImage node.

## Changes

- **What**: Replace `_.isEmpty(workflowData)` check in `handleFile` with
a check for workflow-relevant keys (`workflow`, `prompt`, `parameters`,
`templates`). PNG screenshots often contain non-workflow `tEXt` metadata
(e.g. `Software`, `Creation Time`) which made `_.isEmpty()` return
`false`, skipping the LoadImage fallback and showing an error instead.

## Review Focus

The root cause is that `getPngMetadata` extracts all `tEXt`/`iTXt` PNG
chunks indiscriminately. Rather than filtering at the parser level
(which could break extensions relying on arbitrary metadata), the fix
checks for workflow-relevant keys before deciding whether to treat the
file as a workflow or a plain image.

Fixes #7752

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8886-fix-drag-and-drop-screenshot-creates-LoadImage-node-instead-of-showing-error-3086d73d3650817d86c5f1386aa041c2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-14 22:25:59 -08:00
Jin Yi
2b896a722b [bugfix] Restore scroll-to-setting in SettingDialog (#8833)
## Summary
Restores the scroll-to-setting and highlight animation that was lost
during the BaseModalLayout migration in #8270. Originally implemented in
#8761.

## Changes
- **What**: Re-added scroll-into-view + pulse highlight logic and CSS
animation to `SettingDialog.vue`, ported from the deleted
`SettingDialogContent.vue`

Fixes #3437

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8833-bugfix-Restore-scroll-to-setting-in-SettingDialog-3056d73d36508161abeee047a40dc1e5)
by [Unito](https://www.unito.io)
2026-02-15 03:39:31 +00:00
Jin Yi
96b9e886ea feat: classify missing nodes by replacement availability (#8483)
## Summary
- Extend `MissingNodeType` with `isReplaceable` and `replacement` fields
- Classify missing nodes by checking
`nodeReplacementStore.getReplacementFor()` during graph load
- Wrap hardcoded node patches (T2IAdapterLoader, ConditioningAverage,
etc.) in `if (!isEnabled)` guard so they only run when node replacement
setting is disabled
- Change `useNodeReplacementStore().load()` from fire-and-forget
(`void`) to `await` so replacement data is available before missing node
detection
- Fix guard condition order in `nodeReplacementStore.load()`: check
`isEnabled` before `isLoaded`
- Align `InputMap` types with actual API response (flat
`old_id`/`set_value` fields instead of nested `assign` wrapper)

## Test plan
- [x] Load workflow with deprecated nodes (T2IAdapterLoader,
Load3DAnimation, SDV_img2vid_Conditioning)
- [x] Verify missing nodes are classified with `isReplaceable: true` and
`replacement` object
- [x] Verify hardcoded patches only run when node replacement setting is
OFF
- [x] Verify `nodeReplacementStore.load()` is not called when setting is
disabled
- [x] Unit tests pass (`nodeReplacementStore.test.ts` - 16 tests)
- [x] Typecheck passes

🤖 Generated with [Claude Code](https://claude.ai/code)
2026-02-15 11:26:46 +09:00
Yourz
58182ddda7 fix: skip loading drafts when Comfy.Workflow.Persist is disabled (#8851)
## Summary

Skip draft loading and clear stored drafts when `Comfy.Workflow.Persist`
is disabled, preventing unsaved changes from reappearing.

## Changes

- **What**: Guard draft loading in `ComfyWorkflow.load()` with
`Comfy.Workflow.Persist` setting check. Clear all localStorage drafts
when Persist is toggled from true to false.

## Review Focus

`ComfyWorkflow.load()` previously read drafts unconditionally regardless
of the Persist setting. This meant that after disabling Persist,
previously stored drafts would still be applied when opening a saved
workflow. The fix adds a guard in two places:
1. `comfyWorkflow.ts`: `load()` now checks `Comfy.Workflow.Persist`
before calling `getDraft()`
2. `useWorkflowPersistence.ts`: A `watch` on the Persist setting calls
`draftStore.reset()` when disabled

Fixes Comfy-Org/ComfyUI#12323

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8851-fix-skip-loading-drafts-when-Comfy-Workflow-Persist-is-disabled-3066d73d36508119ac2ce13564e18c01)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-02-15 10:01:54 +08:00
Terry Jia
0f0029ca29 fix: hide output images for ImageCropV2 node (#8873)
## Summary
using the new hideOutputImages flag for image crop.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8873-fix-hide-output-images-for-ImageCropV2-node-3076d73d365081079839dfc9b801606c)
by [Unito](https://www.unito.io)
2026-02-14 17:10:51 -05:00
AustinMroz
ba7f622fbd Fix primitive assets (#8879)
#8598 made primitve widgets connected to an asset have the asset type,
but the `nodeType` parameter required to actually resolve valid models
wasn't getting passed correctly.

This `nodeType`, introduced by me back in #7576, was a mistake. I'm
pulling it out now and instead passing nodeType as an option. Of note:
code changes are only required to pass the option, not to utilize it.

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/f1abfbd1-2502-4b82-841c-7ef697b3a431"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/099cd511-0101-496c-b24e-ee2c19f23384"/>|

The backport PR was made first in #8878. Fixing the bug was time
sensitive and backport would not be clean due to the `widget.value`
changes. Changes were still simpler than expected. I probably should
have made this PR first and then backported, but I misjudged the
complexity of conflict resolution.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8879-Fix-primitive-assets-3076d73d365081b89ed4e6400dbf8e74)
by [Unito](https://www.unito.io)
2026-02-14 12:49:54 -08:00
Benjamin Lu
fcb4341c98 feat(queue): introduce queue notification banners and remove completion summary flow (#8740)
## Summary
Replace the old completion-summary overlay path with queue notification
banners for queueing/completed/failed lifecycle feedback.

## Key changes
- Added `QueueNotificationBanner`, `QueueNotificationBannerHost`,
stories, and tests.
- Added `useQueueNotificationBanners` to handle:
  - immediate `queuedPending` on `promptQueueing`
  - transition to `queued` on `promptQueued` (request-id aware)
  - completed/failed notification sequencing from finished batch history
  - timed notification queueing/dismissal
- Removed completion-summary implementation:
  - `useCompletionSummary`
  - `CompletionSummaryBanner`
  - `QueueOverlayEmpty`
- Simplified `QueueProgressOverlay` to `hidden | active | expanded`
states.
- Top menu behavior:
  - restored `QueueInlineProgressSummary` as separate UI
  - ordering is inline summary first, notification banner below
- notification banner remains under the top menu section (not teleported
to floating actionbar target)
- Kept established API-event signaling pattern
(`promptQueueing`/`promptQueued`) instead of introducing a separate bus.
- Updated tests for top-menu visibility/ordering and notification
behavior across QPOV2 enabled/disabled.

## Notes
- Completion notifications now support stacked thumbnails (cap: 3).
-
https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3843-20314&m=dev

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8740-feat-Queue-Notification-Toasts-3016d73d3650814c8a50d9567a40f44d)
by [Unito](https://www.unito.io)
2026-02-14 12:14:55 -08:00
AustinMroz
27da781029 Fix labels on output slots in vue mode (#8846)
Vue output slots were not respecting the `slot.label`. As a result, a
renamed subgraph output slot would still display the original name when
in vue mode.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8846-Fix-labels-on-output-slots-in-vue-mode-3066d73d3650811c986cffee03f56ac2)
by [Unito](https://www.unito.io)
2026-02-14 10:05:32 -08:00
Christian Byrne
36d59f26cd fix: SaveImage node not updating outputs during batch runs (vue-nodes) (#8862)
## Summary

Fix vue-node outputs not updating during batch runs by creating a new
object reference on merge.

## Changes

- **What**: Spread merged output object in `setOutputsByLocatorId` so
Vue detects the assignment as a change. Adds regression test asserting
reference identity changes on merge.

## Review Focus

One-line fix at `imagePreviewStore.ts:155`: `{ ...existingOutput }`
instead of `existingOutput`. This matches the spread pattern already
used in `restoreOutputs` (line 368).

The root cause: Vue skips reactivity triggers for same-reference
assignments. The merge path mutated `existingOutput` in-place then
reassigned the same object, so `nodeMedia` computed in `LGraphNode.vue`
never re-evaluated.

> Notion:
https://www.notion.so/comfy-org/3066d73d36508165873fcbb9673dece7

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8862-fix-SaveImage-node-not-updating-outputs-during-batch-runs-vue-nodes-3076d73d36508133b1faeae66dcccf01)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2026-02-14 07:16:14 -08:00
Christian Byrne
5f7a6e7aba fix: clear draft on workflow close to prevent stale state on reopen (#8854)
## Summary

Clear the workflow draft from localStorage when any workflow tab is
closed, preventing stale cached state from being served when the
workflow is re-opened.

## Changes

- **What**: `closeWorkflow()` in `workflowStore.ts` now calls
`removeDraft()` for all workflows, not just temporary ones.
`closeWorkflow()` in `workflowService.ts` removes the draft before
switching tabs, preventing `beforeLoadNewGraph()` from re-saving it.

## Review Focus

- Draft is removed before the tab switch in
`workflowService.closeWorkflow()` to prevent `beforeLoadNewGraph()` from
re-saving it during the switch
- Crash recovery is preserved: drafts are only cleared on explicit
close, not on unload/crash
- Tab restore on restart is unaffected: drafts for intentionally-open
tabs are saved on graph change events, not on close

Fixes #8778
Fixes https://github.com/Comfy-Org/ComfyUI/issues/12323

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8854-fix-clear-draft-on-workflow-close-to-prevent-stale-state-on-reopen-3066d73d365081a2a633c9b352d0b0d1)
by [Unito](https://www.unito.io)
2026-02-14 02:50:05 -08:00
Comfy Org PR Bot
2c07bedbb1 1.40.3 (#8859)
Patch version increment to 1.40.3

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8859-1-40-3-3076d73d36508130ab36d2b00a9fb1f3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-14 02:41:47 -08:00
Terry Jia
78635294ce feat: add hideOutputImages flag for nodes with custom preview (#8857)
## Summary

Prerequisite for upcoming native color correction nodes (ColorCorrect,
ColorBalance, ColorCurves) which render their own WebGL preview and need
to suppress the default output image display while keeping data in the
store for downstream nodes.

## Screenshots (if applicable)
before
<img width="980" height="1580" alt="image"
src="https://github.com/user-attachments/assets/2e08869f-1cad-4637-8174-96d034da524c"
/>

after
<img width="783" height="1276" alt="image"
src="https://github.com/user-attachments/assets/3f9b50ee-268c-48f4-9e63-89ef8d732157"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8857-feat-add-hideOutputImages-flag-for-nodes-with-custom-preview-3076d73d365081a2aa55d34280601b47)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-14 03:55:36 -05:00
Alexander Brown
2f09c6321e Regenerate images (#8866)
```
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8866-Regenerate-images-3076d73d365081018009ff8e79c5a418)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-14 00:43:04 -08:00
Christian Byrne
38edba7024 fix: exclude missing assets from cloud mode dropdown (COM-14333) (#8747)
## Summary

Fixes a bug where non-existent images appeared in the asset search
dropdown when loading workflows that reference images the user doesn't
have in cloud mode.

## Changes

- Add `displayItems` prop to `FormDropdown` and `FormDropdownInput` for
showing selected values that aren't in the dropdown list
- Exclude `missingValueItem` from cloud asset mode `dropdownItems` while
still displaying it in the input field via `displayItems`
- Use localized error messages in `ImagePreview` for missing images
(`g.imageDoesNotExist`, `g.unknownFile`)
- Add tests for cloud asset mode behavior in
`WidgetSelectDropdown.test.ts`

## Context

The `missingValueItem` was originally added in PR #8276 for template
workflows. This fix keeps that behavior for local mode but excludes it
from cloud asset mode dropdown. Cloud users can't access files they
don't own, so showing them as search results causes confusion.

## Testing

- Added unit tests for cloud asset mode behavior
- Verified existing tests pass
- All quality gates pass: typecheck, lint, format, tests

Fixes COM-14333



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8747-fix-exclude-missing-assets-from-cloud-mode-dropdown-COM-14333-3016d73d365081e3ab47c326d791257e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-13 14:30:55 -08:00
Comfy Org PR Bot
f851c3189f 1.40.2 (#8842)
Patch version increment to 1.40.2

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8842-1-40-2-3066d73d365081638b92c580d8d8579d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-02-13 08:12:53 -08:00
pythongosssss
71d26eb4d9 Fix storybook build (#8849)
## Summary

The strictExecutionOrder looks to have accidently been removed.
This breaks the storybook build:
https://github.com/vitejs/rolldown-vite/issues/182#issuecomment-3035289157

https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/21979369339/job/63498007630?pr=8842
2026-02-13 15:57:58 +01:00
Terry Jia
d04dd32235 fix: make ResizeHandle interface properties readonly (#8847)
## Summary

improve for https://github.com/Comfy-Org/ComfyUI_frontend/pull/8845

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8847-fix-make-ResizeHandle-interface-properties-readonly-3066d73d3650814da6cee7bb955bbeb7)
by [Unito](https://www.unito.io)
2026-02-12 23:23:44 -05:00
Terry Jia
c52f48af45 feat(vueNodes): support resizing from all four corners (#8845)
## Summary
(Not sure we need this, and I don't know the reason why we only have one
cornor support previously, but it is requested by QA reporting in
Notion)

Add resize handles at all four corners (NW, NE, SW, SE) of Vue nodes,
matching litegraph's multi-corner resize behavior.

Vue nodes previously only supported resizing from the bottom-right (SE)
corner. This adds handles at all four corners with direction-aware delta
math, snap-to-grid support, and minimum size enforcement that keeps the
opposite corner anchored.
The content-driven minimum height is measured from the DOM at resize
start to prevent the node from sliding when dragged past its minimum
size.

## Screenshots (if applicable)


https://github.com/user-attachments/assets/c9d30d93-8243-4c44-a417-5ca6e9978b3b
2026-02-12 22:28:21 -05:00
AustinMroz
01cf3244b8 Fix hover state on collapsed bottom button (#8837)
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/0de0790a-e9f0-432c-9501-eae633e341b6"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/588221b0-b34a-4d35-8393-1bc1ec36c285"
/>|

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8837-Fix-hover-state-on-collapsed-bottom-button-3056d73d36508139919fc1d750c6b135)
by [Unito](https://www.unito.io)
2026-02-12 18:35:37 -08:00
Terry Jia
0f33444eef fix: undo breaking Vue node image preview reactivity (#8839)
## Summary
restoreOutputs was assigning the same object reference to both
app.nodeOutputs and the Pinia reactive ref. This caused subsequent
writes via setOutputsByLocatorId to mutate the reactive proxy's target
through the raw reference before the proxy write, making Vue detect no
change and skip reactivity updates permanently.

Shallow-copy the outputs when assigning to the reactive ref so the proxy
target remains a separate object from app.nodeOutputs.

## Screenshots
before


https://github.com/user-attachments/assets/98f2b17c-87b9-41e7-9caa-238e36c3c032


after


https://github.com/user-attachments/assets/cb6e1d25-bd2e-41ed-a536-7b8250f858ec

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8839-fix-undo-breaking-Vue-node-image-preview-reactivity-3056d73d365081d2a1c7d4d9553f30e0)
by [Unito](https://www.unito.io)
2026-02-12 15:37:02 -05:00
pythongosssss
44ce9379eb Defer vue node layout calculations on hidden browser tabs (#8805)
## Summary

If you load the window in Nodes 2.0, then switch tabs while it is still
loading, the position of the nodes is calculated incorrectly due to
useElementBounding returning left=0, top=0 for the canvas element in a
hidden tab, causing clientPosToCanvasPos to miscalculate node positions
from the ResizeObserver measurements

## Changes

- **What**: 
- Store observed elements while document is in hidden state
- Re-observe when tab becomes visible

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8805-Defer-vue-node-layout-calculations-on-hidden-browser-tabs-3046d73d365081019ae6c403c0ac6d1a)
by [Unito](https://www.unito.io)
2026-02-12 11:48:20 -08:00
pythongosssss
138fa6a2ce Resolve issues with undo with Nodes 2.0 to fix link dragging/rendering (#8808)
## Summary

Resolves the following issue:
1. Enable Nodes 2.0
2. Load default workflow
3. Move any node e.g. VAE decode
4. Undo

All links go invisible, input/output slots no longer function

## Changes

- **What**
- Fixes slot layouts being deleted during undo/redo in
`handleDeleteNode`, which prevented link dragging from nodes after undo.
Vue patches (not remounts) components with the same key, so `onMounted`
never fires to re-register them - these were already being cleared up on
unmounted
- Fixes links disappearing after undo by clearing `pendingSlotSync` when
slot layouts already exist (undo/redo preserved them), rather than
waiting for Vue mounts that do not happen

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8808-Resolve-issues-with-undo-with-Nodes-2-0-to-fix-link-dragging-rendering-3046d73d3650818bbb0adf0104a5792d)
by [Unito](https://www.unito.io)
2026-02-12 11:46:49 -08:00
Comfy Org PR Bot
ce9d0ca670 1.40.1 (#8815)
Patch version increment to 1.40.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8815-1-40-1-3056d73d365081ed8adcf690bd07e1cd)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-12 02:48:38 -08:00
Terry Jia
6cf0357b3e fix(vueNodes): sync node size changes from extensions to Vue components (#7993)
## Summary
When extensions like KJNodes call node.setSize(), the Vue component now
properly updates its CSS variables to reflect the new size.

## Changes:
- LGraphNode pos/size setters now always sync to layoutStore with Canvas
source
- LGraphNode.vue listens to layoutStore changes and updates CSS
variables
- Fixed height calculation to account for NODE_TITLE_HEIGHT difference
- Removed _syncToLayoutStore flag (simplified - layoutStore ignores
non-existent nodes)
- Use setPos() helper method instead of direct pos[0]/pos[1] assignment

## Screenshots (if applicable)
before

https://github.com/user-attachments/assets/236a173a-e41d-485b-8c63-5c28ef1c69bf


after

https://github.com/user-attachments/assets/5fc3f7e4-35c7-40e1-81ac-38a35ee0ac1b

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7993-fix-vueNodes-sync-node-size-changes-from-extensions-to-Vue-components-2e76d73d3650815799c5f2d9d8c7dcbf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-12 05:38:18 -05:00
Christian Byrne
c0c81dba49 fix: rename "Manage Extensions" label to "Extensions" (#8681)
## Summary

Rename the manager button label from "Manage Extensions" to "Extensions"
in both the `menu` and `commands` i18n sections.

## Changes

Updated `manageExtensions` value in `src/locales/en/main.json` (both
`menu` and `commands` sections).

- Fixes follow-up from #8644

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8681-fix-rename-Manage-Extensions-label-to-Extensions-2ff6d73d365081c9aef5c94f745299f6)
by [Unito](https://www.unito.io)
2026-02-12 18:36:07 +09:00
Jin Yi
553ea63357 [refactor] Migrate SettingDialog to BaseModalLayout design system (#8270) 2026-02-12 16:27:11 +09:00
Alexander Brown
995ebc4ba4 fix: default onboardingSurveyEnabled flag to false (#8829)
Fixes race condition where the onboarding survey could appear
intermittently before the actual feature flag value is fetched from the
server.

https://claude.ai/code/session_01EpCtunck6b89gpUFfWGKmZ

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8829-fix-default-onboardingSurveyEnabled-flag-to-false-3056d73d3650819b9f4cd0530efe8f39)
by [Unito](https://www.unito.io)

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-11 23:13:12 -08:00
AustinMroz
d282353370 Add z-index to popover component (#8823)
This fixes the inability to see the value control popover in the
properties panel.
<img width="574" height="760" alt="image"
src="https://github.com/user-attachments/assets/c2cfa00f-f9b1-4c86-abda-830fd780d3f8"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8823-Add-z-index-to-popover-component-3056d73d365081e6b2dbe15517e4c4e0)
by [Unito](https://www.unito.io)
2026-02-11 21:33:05 -08:00
Simula_r
85ae0a57c3 feat: invite member upsell for single-seat plans (#8801)
## Summary

- Show an upsell dialog when single-seat plan users try to invite
members, with a banner on the members panel directing them to upgrade.
- Misc fixes for member max seat display

## Changes

- **What**: `InviteMemberUpsellDialogContent.vue`,
`MembersPanelContent.vue`, `WorkspacePanelContent.vue`

## Screenshots

<img width="2730" height="1907" alt="image"
src="https://github.com/user-attachments/assets/e39a23be-8533-4ebb-a4ae-2797fc382bc2"
/>
<img width="2730" height="1907" alt="image"
src="https://github.com/user-attachments/assets/bec55867-1088-4d3a-b308-5d5cce64c8ae"
/>



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8801-feat-invite-member-upsell-for-single-seat-plans-3046d73d365081349b09fe1d4dc572e8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-11 21:15:59 -08:00
Terry Jia
0d64d503ec fix(vueNodes): propagate height to DOM widget children (#8821)
## Summary
DOM widgets using CSS background-image (e.g. KJNodes FastPreview)
collapsed to 0px height in vueNodes mode because background-image
doesn't contribute to intrinsic element size. Make WidgetDOM a flex
column container so mounted extension elements fill the available grid
row space.

## Screenshots (if applicable)
before 



https://github.com/user-attachments/assets/85de0295-1f7c-4142-ac15-1e813c823e3f


after


https://github.com/user-attachments/assets/824ab662-14cb-412d-93dd-97c0f549f992

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8821-fix-vueNodes-propagate-height-to-DOM-widget-children-3056d73d36508134926dc67336fd0d70)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-11 23:05:54 -05:00
Alexander Brown
30ef6f2b8c Fix: Disabling textarea when linked. (#8818)
## Summary

Misaligned option setting when building the SimplifiedWidget.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8818-Fix-Disabling-textarea-when-linked-3056d73d365081f581a9f1322aaf60bd)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-02-11 18:45:52 -08:00
Alexander Brown
6012341fd1 Fix: Widgets Column sizing and ProgressText Widget (#8817)
## Summary

Fixes an issue where the label/control columns for widgets were
dependent on the contents of the progress text display widget.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8817-Fix-Widgets-Column-sizing-and-ProgressText-Widget-3056d73d36508141a714fe342c386eef)
by [Unito](https://www.unito.io)
2026-02-11 18:45:35 -08:00
Brian Jemilo II
a80f6d7922 Batch Drag & Drop Images (#8282)
## Summary

<!-- One sentence describing what changed and why. -->
Added feature to drag and drop multiple images into the UI and connect
them with a Batch Images node with tests to add convenience for users.
Only works with a group of images, mixing files not supported.

## Review Focus
<!-- Critical design decisions or edge cases that need attention -->
I've updated our usage of Litegraph.createNode, honestly, that method is
pretty bad, onNodeCreated option method doesn't even return the node
created. I think I will probably go check out their repo to do a PR over
there. Anyways, I made a createNode method to avoid race conditions when
creating nodes for the paste actions. Will allow us to better
programmatically create nodes that do not have workflows that also need
to be connected to other nodes.

<!-- If this PR fixes an issue, uncomment and update the line below -->

https://www.notion.so/comfy-org/Implement-Multi-image-drag-and-drop-to-canvas-2eb6d73d36508195ad8addfc4367db10

## Screenshots (if applicable)

https://github.com/user-attachments/assets/d4155807-56e2-4e39-8ab1-16eda90f6a53

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8282-Batch-Drag-Drop-Images-2f16d73d365081c1ab31ce9da47a7be5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Austin Mroz <austin@comfy.org>
2026-02-11 17:39:41 -08:00
Johnpaul Chiwetelu
0f5aca6726 fix: set audio widget value after file upload (#8814)
## Summary

Set `audioWidget.value` after uploading an audio file so the combo
dropdown reflects the newly uploaded file.

## Changes

- **What**: Added `audioWidget.value = path` in the `uploadFile`
function before the callback call, matching the pattern used by the
image upload widget.

## Review Focus

One-line fix. The image upload widget already does this correctly in
`useImageUploadWidget.ts:86`. Without this line, the file uploads but
the dropdown doesn't update to show the selection.

Fixes #8800

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8814-fix-set-audio-widget-value-after-file-upload-3056d73d365081a0af90d4e096eb4975)
by [Unito](https://www.unito.io)
2026-02-11 17:00:28 -08:00
Johnpaul Chiwetelu
4fc1d2ef5b feat: No Explicit Any (#8601)
## Summary
- Add `typescript/no-explicit-any` rule to `.oxlintrc.json` to enforce
no explicit `any` types
- Fix all 40 instances of explicit `any` throughout the codebase
- Improve type safety with proper TypeScript types

## Changes Made

### Configuration
- Added `typescript/no-explicit-any` rule to `.oxlintrc.json`

### Type Fixes
- Replaced `any` with `unknown` for truly unknown types
- Updated generic type parameters to use `unknown` defaults instead of
`any`
- Fixed method `this` parameters to avoid variance issues
- Updated component props to match new generic types
- Fixed test mocks to use proper type assertions

### Key Files Modified
- `src/types/treeExplorerTypes.ts`: Updated TreeExplorerNode interface
generics
- `src/platform/settings/types.ts`: Fixed SettingParams generic default
- `src/lib/litegraph/src/LGraph.ts`: Fixed ParamsArray type constraint
- `src/extensions/core/electronAdapter.ts`: Fixed onChange callbacks
- `src/views/GraphView.vue`: Added proper type imports
- Multiple test files: Fixed type assertions and mocks

## Test Plan
- [x] All lint checks pass (`pnpm lint`)
- [x] TypeScript compilation succeeds (`pnpm typecheck`)
- [x] Pre-commit hooks pass
- [x] No regression in functionality

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8601-feat-add-typescript-no-explicit-any-rule-and-fix-all-instances-2fd6d73d365081fd9beef75d5a6daf5b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-02-12 00:13:48 +01:00
Benjamin Lu
92b7437d86 fix: scope manager button red dot to conflicts (#8810)
## Summary

Scope the top-menu Manager button red dot to manager conflict state
only, so release-update notifications do not appear on the Manager
button.

## Changes

- **What**:
- In `TopMenuSection`, remove release-store coupling and use only
`useConflictAcknowledgment().shouldShowRedDot` for the Manager button
indicator.
- Add a regression test in `TopMenuSection.test.ts` that keeps the
release red dot true while asserting the Manager button dot only appears
when the conflict red dot is true.

## Review Focus

- Confirm Manager button notification semantics are conflict-specific
and no longer mirror release notifications.
- Confirm the new test fails if release-store coupling is reintroduced.

## Screenshots (if applicable)

N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8810-fix-scope-manager-button-red-dot-to-conflicts-3046d73d3650817887b9ca9c33919f48)
by [Unito](https://www.unito.io)
2026-02-11 15:00:25 -08:00
Simula_r
dd1fefe843 fix: credit display and top up and other UI display if personal membe… (#8784)
## Summary

Consolidate scattered role checks for credits, top-up, and subscribe
buttons into centralized workspace permissions (canTopUp,
canManageSubscription), ensuring "Add Credits" requires an active
subscription, subscribe buttons only appear when needed, and team
members see appropriately restricted billing UI.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8784-fix-credit-display-and-top-up-and-other-UI-display-if-personal-membe-3036d73d3650810fbc2de084f738943c)
by [Unito](https://www.unito.io)
2026-02-11 14:26:35 -08:00
Johnpaul Chiwetelu
adcb663b3e fix: link dragging offset on external monitors in Vue nodes mode (#8809)
## Summary

Fix link dragging offset when using Vue nodes mode on external monitors
with different DPI than the primary display.

## Changes

- **What**: Derive overlay canvas scale from actual canvas dimensions
(`canvas.width / canvas.clientWidth`) instead of
`window.devicePixelRatio`, fixing DPR mismatch. Map `LinkDirection.NONE`
to `'none'` in `convertDirection()` instead of falling through to
`'right'`.

## Before


https://github.com/user-attachments/assets/f5d04617-369f-4649-af60-11d31e27a75c



## After


https://github.com/user-attachments/assets/76434d2b-d485-43de-94f6-202a91f73edf


## Review Focus

The overlay canvas copies dimensions from the main canvas (which
includes DPR scaling from `resizeCanvas`). When the page loads on a
monitor whose DPR differs from what `resizeCanvas` used,
`window.devicePixelRatio` no longer matches the canvas's internal-to-CSS
ratio, causing all drawn link positions to be offset. The fix derives
scale directly from the canvas itself.

`LinkDirection.NONE = 0` is falsy, so it was caught by the `default`
case in `convertDirection()`, adding an unwanted directional curve to
moved input links.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8809-fix-link-dragging-offset-on-external-monitors-in-Vue-nodes-mode-3046d73d36508143b600f23f5fe07044)
by [Unito](https://www.unito.io)
2026-02-11 22:40:17 +01:00
AustinMroz
28b171168a New bottom button and badges (#8603)
- "Enter Subgraph" "Show advanced inputs" and a new "show node Errors"
button now use a combined button design at the bottom of the node.
- A new "Errors" tab is added to the right side panel
- After a failed queue, the label of an invalid widget is now red.
- Badges other than price are now displayed on the bottom of the node.
- Price badge will now truncate from the first space, prioritizing the
sizing of the node title
- An indicator for the node resize handle is now displayed while mousing
over the node.

<img width="669" height="233" alt="image"
src="https://github.com/user-attachments/assets/53b3b59c-830b-474d-8f20-07f557124af7"
/>


![resize](https://github.com/user-attachments/assets/e2473b5b-fe4d-4f1e-b1c3-57c23d2a0349)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-10 23:29:45 -08:00
Alexander Brown
69062c6da1 deps: Update vite (#8509)
## Summary

Update from beta.8 to beta.12

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8509-deps-Update-vite-2f96d73d3650814c96dbe14cdfb02151)
by [Unito](https://www.unito.io)
2026-02-10 22:42:26 -08:00
Alexander Brown
a7c2115166 feat: add WidgetValueStore for centralized widget value management (#8594)
## Summary

Implements Phase 1 of the **Vue-owns-truth** pattern for widget values.
Widget values are now canonical in a Pinia store; `widget.value`
delegates to the store while preserving full backward compatibility.

## Changes

- **New store**: `src/stores/widgetValueStore.ts` - centralized widget
value storage with `get/set/remove/removeNode` API
- **BaseWidget integration**: `widget.value` getter/setter now delegates
to store when widget is associated with a node
- **LGraphNode wiring**: `addCustomWidget()` automatically calls
`widget.setNodeId(this.id)` to wire widgets to their nodes
- **Test fixes**: Added Pinia setup to test files that use widgets

## Why

This foundation enables:
- Vue components to reactively bind to widget values via `computed(() =>
store.get(...))`
- Future Yjs/CRDT backing for real-time collaboration
- Cleaner separation between Vue state and LiteGraph rendering

## Backward Compatibility

| Extension Pattern | Status |
|-------------------|--------|
| `widget.value = x` |  Works unchanged |
| `node.widgets[i].value` |  Works unchanged |
| `widget.callback` |  Still fires |
| `node.onWidgetChanged` |  Still fires |

## Testing

-  4252 unit tests pass
-  Build succeeds

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8594-feat-add-WidgetValueStore-for-centralized-widget-value-management-2fc6d73d36508160886fcb9f3ebd941e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-10 19:37:17 -08:00
Csongor Czezar
d044bed9b2 feat: use object info display_name as fallback before node name (#7622)
## Description
Implements fallback to object info display_name before using internal
node name when display_name is unavailable.

## Related Issue
Related to backend PR: comfyanonymous/ComfyUI#11340

## Changes
- Modified `getNodeDefs()` to use object info `display_name` before
falling back to `name`
- Added unit tests for display name fallback behavior

## Testing
- All existing tests pass
- Added 4 new unit tests covering various display_name scenarios

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7622-W-I-P-feat-use-object-info-display_name-as-fallback-before-node-name-2cd6d73d365081deb22fe5ed00e6dc2e)
by [Unito](https://www.unito.io)
2026-02-10 22:18:48 -05:00
Comfy Org PR Bot
d873c8048f 1.40.0 (#8797)
Minor version increment to 1.40.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8797-1-40-0-3046d73d36508109b4b7ed3f6ca4530e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-10 19:06:48 -08:00
Comfy Org PR Bot
475d7035f7 1.39.12 (#8790)
Patch version increment to 1.39.12

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8790-1-39-12-3046d73d3650812faaf5dfaf71f6a02a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-10 18:52:06 -08:00
guill
eb6bf91e20 fix(download): Use content-disposition filename (#8785)
When we download an output, we now check if there's a filename defined
in the content-disposition and use that if there is.

## Summary
This has been primarily an issue on Comfy Cloud where assets are
content-addressed. Before now,
the downloaded files have retained the hash as the filename. With this
change, downloaded files
will use the user-supplied filename instead.

## Changes

- **What**: Use content-disposition filename when downloading assets

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8785-fix-download-Use-content-disposition-filename-3046d73d365081ec952ef3c1930e773d)
by [Unito](https://www.unito.io)
2026-02-10 18:50:42 -08:00
Csongor Czezar
422227d2fc fix: viewport overflow in manager (#7775)
### **Summary**
Fixes viewport overflow in version selector dropdown when Manager dialog
is positioned near edges

**Changes**
Removed popover arrow for visual consistency across all positions
Implemented dialog boundary detection to constrain popover within
Manager viewport

**Testing**
All existing unit tests pass (17/17)
Visually tested across different screen positions


![after-fix-viewport-all-positions](https://github.com/user-attachments/assets/287952f1-eda3-4388-9d6a-8f4316acea7f)

![before-fix-viewport-low](https://github.com/user-attachments/assets/b88dc61d-896b-48af-870f-2b5d52a11a98)

![before-fix-viewport-high](https://github.com/user-attachments/assets/7a39c845-0593-480e-843e-d5da30b48661)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7775-fix-viewport-overflow-in-manager-2d76d73d365081a88c1df2a103c5925e)
by [Unito](https://www.unito.io)
2026-02-10 21:29:44 -05:00
Terry Jia
10e9bc2f8d fix: extract WidgetCallbackOptions interface and add curly braces (#8791)
## Summary

improve for https://github.com/Comfy-Org/ComfyUI_frontend/pull/8774

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8791-fix-extract-WidgetCallbackOptions-interface-and-add-curly-braces-3046d73d365081c49e37c0a2596f1958)
by [Unito](https://www.unito.io)
2026-02-10 17:47:37 -08:00
Terry Jia
f7b835e6a5 fix: disable control after generate during partial execution (#8774)
## Summary
Passes an isPartialExecution flag through widget
beforeQueued/afterQueued callbacks so control-after-generate widgets
skip value modifications (randomize, increment, decrement) when the user
queues selected output nodes via partial execution.

requested by @christian-byrne in notion

## Screenshots (if applicable)
before


https://github.com/user-attachments/assets/3e723087-8849-457b-9f95-b8b5fceab0ed


after


https://github.com/user-attachments/assets/d9816667-51e0-4538-a012-9c84d0944019

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8774-fix-disable-control-after-generate-during-partial-execution-3036d73d365081688ca3d6b0506d69ca)
by [Unito](https://www.unito.io)
2026-02-10 20:13:03 -05:00
Johnpaul Chiwetelu
7f30d6b6a5 feat: add visual indicator for list output slots (#8766)
## Summary

Add rounded square dot shape and "(Iterative)" tooltip for list-type
output slots in Vue nodes, matching litegraph's visual indicator.

## Changes

- **What**: `SlotConnectionDot.vue` renders `rounded-[1px]` instead of
`rounded-full` when slot shape is `RenderShape.GRID`. `OutputSlot.vue`
appends "(Iterative)" to the tooltip for these slots.

<img width="807" height="542" alt="Screenshot 2026-02-10 at 03 38 42"
src="https://github.com/user-attachments/assets/137b60c5-ac3b-457f-a52d-58f5f28a59ea"
/>


## Review Focus
- i18n key added for the iterative suffix

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8766-feat-add-visual-indicator-for-list-output-slots-3036d73d3650813aad85ce094d29c42b)
by [Unito](https://www.unito.io)
2026-02-11 01:49:58 +01:00
Benjamin Lu
da56c9e554 feat: integrate Impact telemetry with checkout attribution for subscriptions (#8688)
Implement Impact telemetry and checkout attribution through cloud
subscription checkout flows.

This PR adds Impact.com tracking support and carries attribution context
from landing-page visits into subscription checkout requests so
conversion attribution can be validated end-to-end.

- Register a new `ImpactTelemetryProvider` during cloud telemetry
initialization.
- Initialize the Impact queue/runtime (`ire`) and load the Universal
Tracking Tag script once.
- Invoke `ire('identify', ...)` on page views with dynamic `customerId`
and SHA-1 `customerEmail` (or empty strings when unknown).
- Expand checkout attribution capture to include `im_ref`, UTM fields,
and Google click IDs, with local persistence across navigation.
- Attempt `ire('generateClickId')` with a timeout and fall back to
URL/local attribution when unavailable.
- Include attribution payloads in checkout creation requests for both:
  - `/customers/cloud-subscription-checkout`
  - `/customers/cloud-subscription-checkout/{tier}`
- Extend begin-checkout telemetry metadata typing to include attribution
fields.
- Add focused unit coverage for provider behavior, attribution
persistence/fallback logic, and checkout request payloads.

Tradeoffs / constraints:
- Attribution collection is treated as best-effort in tiered checkout
flow to avoid blocking purchases.
- Backend checkout handlers must accept and process the additional JSON
attribution fields.

## Screenshots

<img width="908" height="208" alt="image"
src="https://github.com/user-attachments/assets/03c16d60-ffda-40c9-9bd6-8914d841be50"/>
<img width="1144" height="460" alt="image"
src="https://github.com/user-attachments/assets/74b97fde-ce0a-43e6-838e-9a4aba484488"/>
<img width="1432" height="320" alt="image"
src="https://github.com/user-attachments/assets/30c22a9f-7bd8-409f-b0ef-e4d02343780a"/>
<img width="341" height="135" alt="image"
src="https://github.com/user-attachments/assets/f6d918ae-5f80-45e0-855a-601abea61dec"/>
2026-02-10 16:40:51 -08:00
Alexander Brown
79063edf54 Remove comfy logo splash screen. (#8786)
## Summary

```



```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8786-Remove-comfy-logo-splash-screen-3046d73d3650816d92c3ff04afeb8cf6)
by [Unito](https://www.unito.io)
2026-02-10 16:32:18 -08:00
Johnpaul Chiwetelu
d4c40f5255 fix: right-click context menu disabled when selection toolbox is off (#8781)
## Summary

- Move `NodeContextMenu` from `SelectionToolbox.vue` to
`GraphCanvas.vue` so the right-click context menu renders independently
of the `Comfy.Canvas.SelectionToolbox` setting

- Fixes #8417

## Test plan

- [x] Disable selection toolbox in settings, right-click a node —
context menu appears
- [x] Enable selection toolbox, right-click a node — context menu still
appears
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8781-fix-right-click-context-menu-disabled-when-selection-toolbox-is-off-3036d73d36508168a9add58e060b7e93)
by [Unito](https://www.unito.io)
2026-02-11 00:35:52 +01:00
Johnpaul Chiwetelu
1e1d5c8308 fix: stop suppressing link rendering during node resize (#8780)
## Summary

Stop link flickering when resizing nodes by removing the
`pendingSlotSync` flag assertion from `scheduleSlotLayoutSync`.

## Changes

- **What**: Remove `layoutStore.setPendingSlotSync(true)` from
`scheduleSlotLayoutSync()` in `useSlotElementTracking.ts`. This call was
introduced in #8367 for graph reconfiguration but was also triggered on
every node resize, causing all links to disappear for one frame per
resize tick. The reconfigure path in `app.ts`
(`addAfterConfigureHandler`) still sets the flag explicitly, so
undo/redo link suppression is unaffected.

## Review Focus

The `pendingSlotSync` flag is still managed correctly for graph
reconfiguration: `app.ts:748` sets it before configure, and the
`finally` block flushes it synchronously. The
`flushScheduledSlotLayoutSync` early-return (pendingNodes empty but
graph has nodes) continues to handle late-mounting Vue components during
reconfigure.

## Before

https://github.com/user-attachments/assets/28cfe4d8-f3f0-46f1-a717-5cb81a28dd75



## After




https://github.com/user-attachments/assets/9445fd00-91f8-4d1e-90ac-86d138d29842

Fixes #8696

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8780-fix-stop-suppressing-link-rendering-during-node-resize-3036d73d365081029820ccfd57425a07)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-02-11 00:03:40 +01:00
Johnpaul Chiwetelu
e411a104f4 feat: scroll to specific setting when opening settings dialog (#8761)
## Summary

- Adds `settingId` parameter to `showSettingsDialog` that auto-navigates
to the correct category tab, scrolls to the setting, and briefly
highlights it with a CSS pulse animation
- Adds `data-setting-id` attributes to setting items for stable DOM
targeting
- Adds "Don't show this again" checkbox with "Re-enable in Settings"
deep-link to the missing nodes dialog
- Adds "Re-enable in Settings" deep-link to missing models and blueprint
overwrite "Don't show this again" checkboxes

- Fixes #3437

## Test plan

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] Unit tests pass (59/59 including 5 new tests for `useSettingUI`)



https://github.com/user-attachments/assets/a9e80aea-7b69-4686-b030-55a2e0570ff0



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8761-feat-scroll-to-specific-setting-when-opening-settings-dialog-3036d73d365081d18d9afe9f9ed41ebc)
by [Unito](https://www.unito.io)
2026-02-10 23:00:46 +01:00
Terry Jia
19a724710c fix: address review nits in load3d (#8779)
## Summary
- Refactor getModelUrl to use const instead of let
- add missing language key

improve for https://github.com/Comfy-Org/ComfyUI_frontend/pull/8765

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8779-fix-address-review-nits-in-load3d-3036d73d36508183af11c5e9bc545650)
by [Unito](https://www.unito.io)
2026-02-10 16:09:54 -05:00
Terry Jia
9ecbb3af27 Feat/3d dropdown (#8765)
## Summary
Add mesh_upload and upload_subfolder to combo input schema so
WidgetSelect detects mesh uploads generically instead of hardcoding node
type checks. Inject these flags in load3dLazy.ts so they are available
before THREE.js loads.

Also unify SUPPORTED_EXTENSIONS_ACCEPT across load3d and dropdown, pass
uploadSubfolder prop through to WidgetSelectDropdown for correct upload
path, and update error message to list all supported extensions.

replacement for https://github.com/Comfy-Org/ComfyUI_frontend/pull/7975

(We should include thumbnail but not yet, will do it later)

## Screenshots (if applicable)


https://github.com/user-attachments/assets/2cb4b1da-af4f-439b-9786-3ac780c2480d

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8765-Feat-3d-dropdown-3036d73d365081d8a10ee19d3ed7d295)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Kelly Yang <124ykl@gmail.com>
2026-02-10 15:36:57 -05:00
AustinMroz
581452d312 Austin/fix move subgraph input (#8777)
Previously, moving a subgraph input link and re-attaching to the same
input slot would result in an invalid link


![broken-link](https://github.com/user-attachments/assets/085a0a6f-281d-4e06-be58-e5bdc873f1d5)

This occurred because:
- A new link is created to which overwrites the target `input.link`
- The previous link is then disconnected, which clears `input.link`

This is solved by instead returning early if the target is the same as
the existing link.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8777-Austin-fix-move-subgraph-input-3036d73d365081318de3cccb926f7fe7)
by [Unito](https://www.unito.io)
2026-02-10 12:31:09 -08:00
Simula_r
9dde4e7bc7 feat: sort workspaces (#8770)
## Summary

Sort workspaces so that the personal workspace appears first, followed
by the rest in ascending order (oldest first) by created_at / joined_at.

## Changes

- **What**: teamWorkspaceStore.ts, teamWorkspaceStore.test.ts
- **Breaking**: <!-- Any breaking changes (if none, remove this line)
-->
- **Dependencies**: <!-- New dependencies (if none, remove this line)
-->
2026-02-10 10:11:35 -08:00
Johnpaul Chiwetelu
0288ea5b39 feat: add setMany to settingStore for batch setting updates (#8767)
## Summary
- Adds `setMany()` method to `settingStore` for updating multiple
settings in a single API call via the existing `storeSettings` endpoint
- Extracts shared setting-apply logic (`applySettingLocally`) to reduce
duplication between `set()` and `setMany()`
- Migrates all call sites where multiple settings were updated
sequentially to use `setMany()`

## Call sites updated
- `releaseStore.ts` — `handleSkipRelease`, `handleShowChangelog`,
`handleWhatsNewSeen` (3 settings each)
- `keybindingService.ts` — `persistUserKeybindings` (2 settings)
- `coreSettings.ts` — `NavigationMode.onChange` (2 settings)

## Test plan
- [x] Unit tests for `setMany` (batch update, skip unchanged, no-op when
unchanged)
- [x] Updated `releaseStore.test.ts` assertions to verify `setMany`
usage
- [x] Updated `useCoreCommands.test.ts` mock to include `setMany`
- [x] All existing tests pass
- [x] `pnpm typecheck`, `pnpm lint`, `pnpm format` pass

Fixes #1079

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8767-feat-add-setMany-to-settingStore-for-batch-setting-updates-3036d73d36508161b8b6d298e1be1b7a)
by [Unito](https://www.unito.io)
2026-02-10 13:47:53 +01:00
Comfy Org PR Bot
061e96e488 1.39.11 (#8763)
Patch version increment to 1.39.11

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8763-1-39-11-3036d73d365081458389fe558cd921ee)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2026-02-09 21:28:11 -08:00
748 changed files with 52767 additions and 11384 deletions

View File

@@ -34,10 +34,13 @@ jobs:
- name: Build project
run: pnpm build
env:
DISTRIBUTION: localhost
- name: Scan dist for telemetry references
- name: Scan dist for GTM telemetry references
run: |
set -euo pipefail
echo '🔍 Scanning for Google Tag Manager references...'
if rg --no-ignore -n \
-g '*.html' \
-g '*.js' \
@@ -46,7 +49,33 @@ jobs:
-e '(?i)googletagmanager\.com/gtm\.js\\?id=' \
-e '(?i)googletagmanager\.com/ns\.html\\?id=' \
dist; then
echo 'Telemetry references found in dist assets.'
echo '❌ ERROR: Google Tag Manager references found in dist assets!'
echo 'GTM must be properly tree-shaken from OSS builds.'
exit 1
fi
echo 'No telemetry references found in dist assets.'
echo 'No GTM references found'
- name: Scan dist for Mixpanel telemetry references
run: |
set -euo pipefail
echo '🔍 Scanning for Mixpanel references...'
if rg --no-ignore -n \
-g '*.html' \
-g '*.js' \
-e '(?i)mixpanel\.init' \
-e '(?i)mixpanel\.identify' \
-e 'MixpanelTelemetryProvider' \
-e 'mp\.comfy\.org' \
-e 'mixpanel-browser' \
-e '(?i)mixpanel\.track\(' \
dist; then
echo '❌ ERROR: Mixpanel references found in dist assets!'
echo 'Mixpanel must be properly tree-shaken from OSS builds.'
echo ''
echo 'To fix this:'
echo '1. Use the TelemetryProvider pattern (see src/platform/telemetry/)'
echo '2. Call telemetry via useTelemetry() hook'
echo '3. Use conditional dynamic imports behind isCloud checks'
exit 1
fi
echo '✅ No Mixpanel references found'

View File

@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
token: ${{ secrets.PR_GH_TOKEN }}
token: ${{ !github.event.pull_request.head.repo.fork && secrets.PR_GH_TOKEN || github.token }}
- name: Setup frontend
uses: ./.github/actions/setup-frontend

View File

@@ -96,6 +96,7 @@
"typescript/restrict-template-expressions": "off",
"typescript/unbound-method": "off",
"typescript/no-floating-promises": "error",
"typescript/no-explicit-any": "error",
"vue/no-import-compiler-macros": "error",
"vue/no-dupe-keys": "error"
},

View File

@@ -98,12 +98,10 @@ const config: StorybookConfig = {
},
build: {
rolldownOptions: {
experimental: {
strictExecutionOrder: true
},
treeshake: false,
output: {
keepNames: true
keepNames: true,
strictExecutionOrder: true
},
onwarn: (warning, warn) => {
// Suppress specific warnings

View File

@@ -90,7 +90,6 @@ const preview: Preview = {
{ value: 'light', icon: 'sun', title: 'Light' },
{ value: 'dark', icon: 'moon', title: 'Dark' }
],
showName: true,
dynamicTitle: true
}
}

View File

@@ -52,7 +52,8 @@
"reference",
"plugin",
"custom-variant",
"utility"
"utility",
"source"
]
}
],

View File

@@ -0,0 +1,205 @@
{
"last_node_id": 7,
"last_link_id": 5,
"nodes": [
{
"id": 1,
"type": "T2IAdapterLoader",
"pos": [100, 100],
"size": [300, 80],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "CONTROL_NET",
"type": "CONTROL_NET",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "T2IAdapterLoader"
},
"widgets_values": ["t2iadapter_model.safetensors"]
},
{
"id": 2,
"type": "CheckpointLoaderSimple",
"pos": [100, 300],
"size": [315, 98],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"]
},
{
"id": 3,
"type": "ResizeImagesByLongerEdge",
"pos": [500, 100],
"size": [300, 80],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [1],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ResizeImagesByLongerEdge"
},
"widgets_values": [1024]
},
{
"id": 4,
"type": "ImageScaleBy",
"pos": [500, 280],
"size": [300, 80],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 1
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [2, 3],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageScaleBy"
},
"widgets_values": ["lanczos", 1.5]
},
{
"id": 5,
"type": "ImageBatch",
"pos": [900, 100],
"size": [300, 80],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image1",
"type": "IMAGE",
"link": 2
},
{
"name": "image2",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [4],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageBatch"
},
"widgets_values": []
},
{
"id": 6,
"type": "SaveImage",
"pos": [900, 300],
"size": [300, 80],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 3
}
],
"properties": {
"Node name for S&R": "SaveImage"
},
"widgets_values": ["ComfyUI"]
},
{
"id": 7,
"type": "PreviewImage",
"pos": [1250, 100],
"size": [300, 250],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4
}
],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
}
],
"links": [
[1, 3, 0, 4, 0, "IMAGE"],
[2, 4, 0, 5, 0, "IMAGE"],
[3, 4, 0, 6, 0, "IMAGE"],
[4, 5, 0, 7, 0, "IMAGE"]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [0, 0]
}
},
"version": 0.4
}

View File

@@ -0,0 +1,186 @@
{
"last_node_id": 5,
"last_link_id": 2,
"nodes": [
{
"id": 1,
"type": "Load3DAnimation",
"pos": [100, 100],
"size": [300, 100],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MESH",
"type": "MESH",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Load3DAnimation"
},
"widgets_values": ["model.glb"]
},
{
"id": 2,
"type": "Preview3DAnimation",
"pos": [450, 100],
"size": [300, 100],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "mesh",
"type": "MESH",
"link": null
}
],
"properties": {
"Node name for S&R": "Preview3DAnimation"
},
"widgets_values": []
},
{
"id": 3,
"type": "ConditioningAverage ",
"pos": [100, 300],
"size": [300, 100],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "conditioning_to",
"type": "CONDITIONING",
"link": null
},
{
"name": "conditioning_from",
"type": "CONDITIONING",
"link": null
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [1],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ConditioningAverage "
},
"widgets_values": [1]
},
{
"id": 4,
"type": "SDV_img2vid_Conditioning",
"pos": [450, 300],
"size": [300, 150],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip_vision",
"type": "CLIP_VISION",
"link": null
},
{
"name": "init_image",
"type": "IMAGE",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [],
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [],
"slot_index": 1
},
{
"name": "latent",
"type": "LATENT",
"links": [2],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "SDV_img2vid_Conditioning"
},
"widgets_values": [1024, 576, 14, 127, 25, 0.02]
},
{
"id": 5,
"type": "KSampler",
"pos": [800, 300],
"size": [300, 262],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 1
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [42, "fixed", 20, 8, "euler", "normal", 1]
}
],
"links": [
[1, 3, 0, 5, 1, "CONDITIONING"],
[2, 4, 2, 5, 3, "LATENT"]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [0, 0]
}
},
"version": 0.4
}

View File

@@ -0,0 +1,86 @@
{
"id": "save-image-and-webm-test",
"revision": 0,
"last_node_id": 12,
"last_link_id": 2,
"nodes": [
{
"id": 10,
"type": "LoadImage",
"pos": [50, 100],
"size": [315, 314],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [1, 2]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": ["example.png", "image"]
},
{
"id": 11,
"type": "SaveImage",
"pos": [450, 100],
"size": [210, 270],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 1
}
],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 12,
"type": "SaveWEBM",
"pos": [450, 450],
"size": [210, 368],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2
}
],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", "vp9", 6, 32]
}
],
"links": [
[1, 10, 0, 11, 0, "IMAGE"],
[2, 10, 0, 12, 0, "IMAGE"]
],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.17.0",
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -14,6 +14,7 @@ import { ComfyTemplates } from '../helpers/templates'
import { ComfyMouse } from './ComfyMouse'
import { VueNodeHelpers } from './VueNodeHelpers'
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
import { ComfyNodeSearchBoxV2 } from './components/ComfyNodeSearchBoxV2'
import { ContextMenu } from './components/ContextMenu'
import { SettingDialog } from './components/SettingDialog'
import { BottomPanel } from './components/BottomPanel'
@@ -166,6 +167,7 @@ export class ComfyPage {
// Components
public readonly searchBox: ComfyNodeSearchBox
public readonly searchBoxV2: ComfyNodeSearchBoxV2
public readonly menu: ComfyMenu
public readonly actionbar: ComfyActionbar
public readonly templates: ComfyTemplates
@@ -210,6 +212,7 @@ export class ComfyPage {
this.workflowUploadInput = page.locator('#comfy-file-input')
this.searchBox = new ComfyNodeSearchBox(page)
this.searchBoxV2 = new ComfyNodeSearchBoxV2(page)
this.menu = new ComfyMenu(page)
this.actionbar = new ComfyActionbar(page)
this.templates = new ComfyTemplates(page)

View File

@@ -0,0 +1,29 @@
import type { Locator, Page } from '@playwright/test'
import type { ComfyPage } from '../ComfyPage'
export class ComfyNodeSearchBoxV2 {
readonly dialog: Locator
readonly input: Locator
readonly results: Locator
readonly filterOptions: Locator
constructor(readonly page: Page) {
this.dialog = page.getByRole('search')
this.input = this.dialog.locator('input[type="text"]')
this.results = this.dialog.getByTestId('result-item')
this.filterOptions = this.dialog.getByTestId('filter-option')
}
categoryButton(categoryId: string): Locator {
return this.dialog.getByTestId(`category-${categoryId}`)
}
filterBarButton(name: string): Locator {
return this.dialog.getByRole('button', { name })
}
async reload(comfyPage: ComfyPage) {
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
}
}

View File

@@ -23,9 +23,7 @@ export class SettingDialog extends BaseDialog {
* @param value - The value to set
*/
async setStringSetting(id: string, value: string) {
const settingInputDiv = this.page.locator(
`div.settings-container div[id="${id}"]`
)
const settingInputDiv = this.root.locator(`div[id="${id}"]`)
await settingInputDiv.locator('input').fill(value)
}
@@ -34,16 +32,31 @@ export class SettingDialog extends BaseDialog {
* @param id - The id of the setting
*/
async toggleBooleanSetting(id: string) {
const settingInputDiv = this.page.locator(
`div.settings-container div[id="${id}"]`
)
const settingInputDiv = this.root.locator(`div[id="${id}"]`)
await settingInputDiv.locator('input').click()
}
get searchBox() {
return this.root.getByPlaceholder(/Search/)
}
get categories() {
return this.root.locator('nav').getByRole('button')
}
category(name: string) {
return this.root.locator('nav').getByRole('button', { name })
}
get contentArea() {
return this.root.getByRole('main')
}
async goToAboutPanel() {
await this.page.getByTestId(TestIds.dialogs.settingsTabAbout).click()
await this.page
.getByTestId(TestIds.dialogs.about)
.waitFor({ state: 'visible' })
const aboutButton = this.root.locator('nav').getByRole('button', {
name: 'About'
})
await aboutButton.click()
await this.page.waitForSelector('.about-container')
}
}

View File

@@ -226,9 +226,7 @@ test.describe('Bottom Panel Shortcuts', { tag: '@ui' }, () => {
await expect(bottomPanel.shortcuts.manageButton).toBeVisible()
await bottomPanel.shortcuts.manageButton.click()
await expect(comfyPage.page.getByRole('dialog')).toBeVisible()
await expect(
comfyPage.page.getByRole('option', { name: 'Keybinding' })
).toBeVisible()
await expect(comfyPage.settingDialog.root).toBeVisible()
await expect(comfyPage.settingDialog.category('Keybinding')).toBeVisible()
})
})

View File

@@ -244,21 +244,9 @@ test.describe(
await comfyPage.settings.setSetting('Comfy.Node.Opacity', 0.5)
await comfyPage.settings.setSetting('Comfy.ColorPalette', 'light')
await comfyPage.nextFrame()
const parsed = await (
await comfyPage.page.waitForFunction(
() => {
const workflow = localStorage.getItem('workflow')
if (!workflow) return null
try {
const data = JSON.parse(workflow)
return Array.isArray(data?.nodes) ? data : null
} catch {
return null
}
},
{ timeout: 3000 }
)
).jsonValue()
const parsed = await comfyPage.page.evaluate(() => {
return window['app'].graph.serialize()
})
expect(parsed.nodes).toBeDefined()
expect(Array.isArray(parsed.nodes)).toBe(true)
for (const node of parsed.nodes) {

View File

@@ -0,0 +1,32 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.describe('Confirm dialog text wrapping', { tag: ['@mobile'] }, () => {
test('@mobile confirm dialog buttons are visible with long unbreakable text', async ({
comfyPage
}) => {
const longFilename = 'workflow_checkpoint_' + 'a'.repeat(200) + '.json'
await comfyPage.page.evaluate((msg) => {
window
.app!.extensionManager.dialog.confirm({
title: 'Confirm',
type: 'default',
message: msg
})
.catch(() => {})
}, longFilename)
const dialog = comfyPage.page.getByRole('dialog')
await expect(dialog).toBeVisible()
const confirmButton = dialog.getByRole('button', { name: 'Confirm' })
await expect(confirmButton).toBeVisible()
await expect(confirmButton).toBeInViewport()
const cancelButton = dialog.getByRole('button', { name: 'Cancel' })
await expect(cancelButton).toBeVisible()
await expect(cancelButton).toBeInViewport()
})
})

View File

@@ -37,7 +37,7 @@ test.describe('Load workflow warning', { tag: '@ui' }, () => {
})
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'v1 (legacy)')
await comfyPage.workflow.loadWorkflow('missing/missing_nodes')
await comfyPage.page
@@ -61,16 +61,21 @@ test('Does not report warning on undo/redo', async ({ comfyPage }) => {
})
test.describe('Execution error', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setup()
})
test('Should display an error message when an execution error occurs', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('nodes/execution_error')
await comfyPage.queueButton.click()
await comfyPage.command.executeCommand('Comfy.QueuePrompt')
await comfyPage.nextFrame()
// Wait for the element with the .comfy-execution-error selector to be visible
const executionError = comfyPage.page.locator('.comfy-error-report')
await expect(executionError).toBeVisible()
// Wait for the error overlay to be visible
const errorOverlay = comfyPage.page.locator('[data-testid="error-overlay"]')
await expect(errorOverlay).toBeVisible()
})
})
@@ -244,9 +249,13 @@ test.describe('Missing models warning', () => {
test.describe('Settings', () => {
test('@mobile Should be visible on mobile', async ({ comfyPage }) => {
await comfyPage.page.keyboard.press('Control+,')
const settingsContent = comfyPage.page.locator('.settings-content')
await expect(settingsContent).toBeVisible()
const isUsableHeight = await settingsContent.evaluate(
const settingsDialog = comfyPage.page.locator(
'[data-testid="settings-dialog"]'
)
await expect(settingsDialog).toBeVisible()
const contentArea = settingsDialog.locator('main')
await expect(contentArea).toBeVisible()
const isUsableHeight = await contentArea.evaluate(
(el) => el.clientHeight > 30
)
expect(isUsableHeight).toBeTruthy()
@@ -256,7 +265,9 @@ test.describe('Settings', () => {
await comfyPage.page.keyboard.down('ControlOrMeta')
await comfyPage.page.keyboard.press(',')
await comfyPage.page.keyboard.up('ControlOrMeta')
const settingsLocator = comfyPage.page.locator('.settings-container')
const settingsLocator = comfyPage.page.locator(
'[data-testid="settings-dialog"]'
)
await expect(settingsLocator).toBeVisible()
await comfyPage.page.keyboard.press('Escape')
await expect(settingsLocator).not.toBeVisible()
@@ -275,10 +286,15 @@ test.describe('Settings', () => {
test('Should persist keybinding setting', async ({ comfyPage }) => {
// Open the settings dialog
await comfyPage.page.keyboard.press('Control+,')
await comfyPage.page.waitForSelector('.settings-container')
await comfyPage.page.waitForSelector('[data-testid="settings-dialog"]')
// Open the keybinding tab
await comfyPage.page.getByLabel('Keybinding').click()
const settingsDialog = comfyPage.page.locator(
'[data-testid="settings-dialog"]'
)
await settingsDialog
.locator('nav [role="button"]', { hasText: 'Keybinding' })
.click()
await comfyPage.page.waitForSelector(
'[placeholder="Search Keybindings..."]'
)
@@ -298,7 +314,10 @@ test.describe('Settings', () => {
await input.press('Alt+n')
const requestPromise = comfyPage.page.waitForRequest(
'**/api/settings/Comfy.Keybinding.NewBindings'
(req) =>
req.url().includes('/api/settings') &&
!req.url().includes('/api/settings/') &&
req.method() === 'POST'
)
// Save keybinding
@@ -326,17 +345,23 @@ test.describe('Support', () => {
comfyPage
}) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
const pagePromise = comfyPage.page.context().waitForEvent('page')
// Prevent loading the external page
await comfyPage.page
.context()
.route('https://support.comfy.org/**', (route) =>
route.fulfill({ body: '<html></html>', contentType: 'text/html' })
)
const popupPromise = comfyPage.page.waitForEvent('popup')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Support'])
const newPage = await pagePromise
const popup = await popupPromise
await newPage.waitForLoadState('networkidle')
await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/)
const url = new URL(newPage.url())
const url = new URL(popup.url())
expect(url.hostname).toBe('support.comfy.org')
expect(url.searchParams.get('tf_42243568391700')).toBe('oss')
await newPage.close()
await popup.close()
})
})

View File

@@ -7,22 +7,29 @@ test.beforeEach(async ({ comfyPage }) => {
})
test.describe('Execution', { tag: ['@smoke', '@workflow'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setup()
})
test(
'Report error on unconnected slot',
{ tag: '@screenshot' },
async ({ comfyPage }) => {
await comfyPage.canvasOps.disconnectEdge()
await comfyPage.canvasOps.clickEmptySpace()
await comfyPage.page.keyboard.press('Escape')
await comfyPage.command.executeCommand('Comfy.QueuePrompt')
await expect(comfyPage.page.locator('.comfy-error-report')).toBeVisible()
await expect(
comfyPage.page.locator('[data-testid="error-overlay"]')
).toBeVisible()
await comfyPage.page
.locator('.p-dialog')
.getByRole('button', { name: 'Close' })
.locator('[data-testid="error-overlay"]')
.getByRole('button', { name: 'Dismiss' })
.click()
await comfyPage.page.locator('.comfy-error-report').waitFor({
state: 'hidden'
})
await comfyPage.page
.locator('[data-testid="error-overlay"]')
.waitFor({ state: 'hidden' })
await expect(comfyPage.canvas).toHaveScreenshot(
'execution-error-unconnected-slot.png'
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -37,12 +37,9 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
// Monitor for server feature flags
const checkInterval = setInterval(() => {
if (
window.app?.api?.serverFeatureFlags &&
Object.keys(window.app.api.serverFeatureFlags).length > 0
) {
window.__capturedMessages!.serverFeatureFlags =
window.app.api.serverFeatureFlags
const flags = window.app?.api?.serverFeatureFlags?.value
if (flags && Object.keys(flags).length > 0) {
window.__capturedMessages!.serverFeatureFlags = flags
clearInterval(checkInterval)
}
}, 100)
@@ -96,7 +93,7 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
}) => {
// Get the actual server feature flags from the backend
const serverFlags = await comfyPage.page.evaluate(() => {
return window.app!.api.serverFeatureFlags
return window.app!.api.serverFeatureFlags.value
})
// Verify we received real feature flags from the backend
@@ -129,8 +126,8 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
// Test that the method only returns true for boolean true values
const testResults = await comfyPage.page.evaluate(() => {
// Temporarily modify serverFeatureFlags to test behavior
const original = window.app!.api.serverFeatureFlags
window.app!.api.serverFeatureFlags = {
const original = window.app!.api.serverFeatureFlags.value
window.app!.api.serverFeatureFlags.value = {
bool_true: true,
bool_false: false,
string_value: 'yes',
@@ -147,7 +144,7 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
}
// Restore original
window.app!.api.serverFeatureFlags = original
window.app!.api.serverFeatureFlags.value = original
return results
})
@@ -282,8 +279,8 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
// Monitor when feature flags arrive by checking periodically
const checkFeatureFlags = setInterval(() => {
if (
window.app?.api?.serverFeatureFlags?.supports_preview_metadata !==
undefined
window.app?.api?.serverFeatureFlags?.value
?.supports_preview_metadata !== undefined
) {
window.__appReadiness!.featureFlagsReceived = true
clearInterval(checkFeatureFlags)
@@ -320,8 +317,8 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
// Wait for feature flags to be received
await newPage.waitForFunction(
() =>
window.app?.api?.serverFeatureFlags?.supports_preview_metadata !==
undefined,
window.app?.api?.serverFeatureFlags?.value
?.supports_preview_metadata !== undefined,
{
timeout: 10000
}
@@ -331,7 +328,7 @@ test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
const readiness = await newPage.evaluate(() => {
return {
...window.__appReadiness,
currentFlags: window.app!.api.serverFeatureFlags
currentFlags: window.app!.api.serverFeatureFlags.value
}
})

View File

@@ -10,6 +10,8 @@ import type { NodeReference } from '../fixtures/utils/litegraphUtils'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'v1 (legacy)')
})
test.describe('Group Node', { tag: '@node' }, () => {

View File

@@ -13,6 +13,9 @@ import type { NodeReference } from '../fixtures/utils/litegraphUtils'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
// Wait for the legacy menu to appear and canvas to settle after layout shift.
await comfyPage.page.locator('.comfy-menu').waitFor({ state: 'visible' })
await comfyPage.nextFrame()
})
test.describe('Item Interaction', { tag: ['@screenshot', '@node'] }, () => {
@@ -733,6 +736,25 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
)
// Wait for V2 persistence debounce to save the modified workflow
const start = Date.now()
await comfyPage.page.waitForFunction((since) => {
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i)
if (!key?.startsWith('Comfy.Workflow.DraftIndex.v2:')) continue
const json = window.localStorage.getItem(key)
if (!json) continue
try {
const index = JSON.parse(json)
if (typeof index.updatedAt === 'number' && index.updatedAt >= since) {
return true
}
} catch {
// ignore
}
}
return false
}, start)
await comfyPage.setup({ clearStorage: false })
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
@@ -755,10 +777,17 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
await comfyPage.menu.topbar.saveWorkflow(workflowB)
// Wait for localStorage to persist the workflow paths before reloading
await comfyPage.page.waitForFunction(
() => !!window.localStorage.getItem('Comfy.OpenWorkflowsPaths')
)
// Wait for sessionStorage to persist the workflow paths before reloading
// V2 persistence uses sessionStorage with client-scoped keys
await comfyPage.page.waitForFunction(() => {
for (let i = 0; i < window.sessionStorage.length; i++) {
const key = window.sessionStorage.key(i)
if (key?.startsWith('Comfy.Workflow.OpenPaths:')) {
return true
}
}
return false
})
await comfyPage.setup({ clearStorage: false })
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -27,6 +27,7 @@ test.describe('Node Help', { tag: ['@slow', '@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setup()
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
})
test.describe('Selection Toolbox', () => {

View File

@@ -18,7 +18,10 @@ test.describe('Node search box', { tag: '@node' }, () => {
'Comfy.LinkRelease.ActionShift',
'search box'
)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
})
test(`Can trigger on empty canvas double click`, async ({ comfyPage }) => {
@@ -45,7 +48,10 @@ test.describe('Node search box', { tag: '@node' }, () => {
await comfyPage.setup({ clearStorage: true })
// Simulate new user with 1.24.1+ installed version
await comfyPage.settings.setSetting('Comfy.InstalledVersion', '1.24.1')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
// Don't set LinkRelease settings explicitly to test versioned defaults
await comfyPage.canvasOps.disconnectEdge()
@@ -215,6 +221,14 @@ test.describe('Node search box', { tag: '@node' }, () => {
await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
})
test('Does not add duplicate filter with same type and value', async ({
comfyPage
}) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await expectFilterChips(comfyPage, ['MODEL'])
})
test('Can remove filter', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.removeFilter(0)
@@ -277,7 +291,10 @@ test.describe('Release context menu', { tag: '@node' }, () => {
'Comfy.LinkRelease.ActionShift',
'search box'
)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
})
test(
@@ -321,7 +338,10 @@ test.describe('Release context menu', { tag: '@node' }, () => {
await comfyPage.setup({ clearStorage: true })
// Simulate existing user with pre-1.24.1 version
await comfyPage.settings.setSetting('Comfy.InstalledVersion', '1.23.0')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
// Don't set LinkRelease settings explicitly to test versioned defaults
await comfyPage.canvasOps.disconnectEdge()
@@ -342,7 +362,10 @@ test.describe('Release context menu', { tag: '@node' }, () => {
'Comfy.LinkRelease.Action',
'context menu'
)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
await comfyPage.canvasOps.disconnectEdge()
// Context menu should appear due to explicit setting, not search box

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -0,0 +1,149 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../fixtures/ComfyPage'
test.describe('Node search box V2', { tag: '@node' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.LinkRelease.Action',
'search box'
)
await comfyPage.settings.setSetting(
'Comfy.LinkRelease.ActionShift',
'search box'
)
await comfyPage.searchBoxV2.reload(comfyPage)
})
test('Can open search and add node', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
await searchBoxV2.input.fill('KSampler')
await expect(searchBoxV2.results.first()).toBeVisible()
await comfyPage.page.keyboard.press('Enter')
await expect(searchBoxV2.input).not.toBeVisible()
const newCount = await comfyPage.nodeOps.getGraphNodesCount()
expect(newCount).toBe(initialCount + 1)
})
test('Can add first default result with Enter', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
// Default results should be visible without typing
await expect(searchBoxV2.results.first()).toBeVisible()
// Enter should add the first (selected) result
await comfyPage.page.keyboard.press('Enter')
await expect(searchBoxV2.input).not.toBeVisible()
const newCount = await comfyPage.nodeOps.getGraphNodesCount()
expect(newCount).toBe(initialCount + 1)
})
test.describe('Category navigation', () => {
test('Favorites shows only bookmarked nodes', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
await comfyPage.settings.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [
'KSampler'
])
await searchBoxV2.reload(comfyPage)
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
await searchBoxV2.categoryButton('favorites').click()
await expect(searchBoxV2.results).toHaveCount(1)
await expect(searchBoxV2.results.first()).toContainText('KSampler')
})
test('Category filters results to matching nodes', async ({
comfyPage
}) => {
const { searchBoxV2 } = comfyPage
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
await searchBoxV2.categoryButton('sampling').click()
await expect(searchBoxV2.results.first()).toBeVisible()
const count = await searchBoxV2.results.count()
expect(count).toBeGreaterThan(0)
})
})
test.describe('Filter workflow', () => {
test('Can filter by input type via filter bar', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
// Click "Input" filter chip in the filter bar
await searchBoxV2.filterBarButton('Input').click()
// Filter options should appear
await expect(searchBoxV2.filterOptions.first()).toBeVisible()
// Type to narrow and select MODEL
await searchBoxV2.input.fill('MODEL')
await searchBoxV2.filterOptions
.filter({ hasText: 'MODEL' })
.first()
.click()
// Filter chip should appear and results should be filtered
await expect(
searchBoxV2.dialog.getByText('Input:', { exact: false }).locator('..')
).toContainText('MODEL')
await expect(searchBoxV2.results.first()).toBeVisible()
})
})
test.describe('Keyboard navigation', () => {
test('Can navigate and select with keyboard', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
await comfyPage.canvasOps.doubleClick()
await expect(searchBoxV2.input).toBeVisible()
await searchBoxV2.input.fill('KSampler')
const results = searchBoxV2.results
await expect(results.first()).toBeVisible()
// First result selected by default
await expect(results.first()).toHaveAttribute('aria-selected', 'true')
// ArrowDown moves selection
await comfyPage.page.keyboard.press('ArrowDown')
await expect(results.nth(1)).toHaveAttribute('aria-selected', 'true')
await expect(results.first()).toHaveAttribute('aria-selected', 'false')
// ArrowUp moves back
await comfyPage.page.keyboard.press('ArrowUp')
await expect(results.first()).toHaveAttribute('aria-selected', 'true')
// Enter selects and adds node
await comfyPage.page.keyboard.press('Enter')
await expect(searchBoxV2.input).not.toBeVisible()
const newCount = await comfyPage.nodeOps.getGraphNodesCount()
expect(newCount).toBe(initialCount + 1)
})
})
})

View File

@@ -5,6 +5,7 @@ import { TestIds } from '../../fixtures/selectors'
test.describe('Properties panel position', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
// Open a sidebar tab to ensure sidebar is visible
await comfyPage.menu.nodeLibraryTab.open()
await comfyPage.actionbar.propertiesButton.click()

View File

@@ -4,6 +4,7 @@ import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'v1 (legacy)')
})
test.describe('Record Audio Node', { tag: '@screenshot' }, () => {

View File

@@ -53,6 +53,11 @@ test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
})
test.describe('Loading options', () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -0,0 +1,42 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.describe(
'Save Image and WEBM preview',
{ tag: ['@screenshot', '@widget'] },
() => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('Can preview both SaveImage and SaveWEBM outputs', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow(
'widgets/save_image_and_animated_webp'
)
await comfyPage.vueNodes.waitForNodes()
await comfyPage.runButton.click()
const saveImageNode = comfyPage.vueNodes.getNodeByTitle('Save Image')
const saveWebmNode = comfyPage.vueNodes.getNodeByTitle('SaveWEBM')
// Wait for SaveImage to render an img inside .image-preview
await expect(saveImageNode.locator('.image-preview img')).toBeVisible({
timeout: 30000
})
// Wait for SaveWEBM to render a video inside .video-preview
await expect(saveWebmNode.locator('.video-preview video')).toBeVisible({
timeout: 30000
})
await expect(comfyPage.page).toHaveScreenshot(
'save-image-and-webm-preview.png'
)
})
}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -5,6 +5,7 @@ import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Node library sidebar', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
await comfyPage.settings.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [])
await comfyPage.settings.setSetting(
'Comfy.NodeLibrary.BookmarksCustomization',

View File

@@ -5,13 +5,6 @@ import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.describe('Subgraph duplicate ID remapping', { tag: ['@subgraph'] }, () => {
const WORKFLOW = 'subgraphs/subgraph-nested-duplicate-ids'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting(
'Comfy.Graph.DeduplicateSubgraphNodeIds',
true
)
})
test('All node IDs are globally unique after loading', async ({
comfyPage
}) => {

View File

@@ -19,6 +19,10 @@ const SELECTORS = {
test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
})
// Helper to get subgraph slot count
@@ -61,7 +65,10 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
await subgraphNode.navigateIntoSubgraph()
const initialCount = await getSubgraphSlotCount(comfyPage, 'inputs')
const vaeEncodeNode = await comfyPage.nodeOps.getNodeRefById('2')
const [vaeEncodeNode] = await comfyPage.nodeOps.getNodeRefsByType(
'VAEEncode',
true
)
await comfyPage.subgraph.connectFromInput(vaeEncodeNode, 0)
await comfyPage.nextFrame()
@@ -77,7 +84,10 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
await subgraphNode.navigateIntoSubgraph()
const initialCount = await getSubgraphSlotCount(comfyPage, 'outputs')
const vaeEncodeNode = await comfyPage.nodeOps.getNodeRefById('2')
const [vaeEncodeNode] = await comfyPage.nodeOps.getNodeRefsByType(
'VAEEncode',
true
)
await comfyPage.subgraph.connectToOutput(vaeEncodeNode, 0)
await comfyPage.nextFrame()
@@ -820,7 +830,7 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
// Open settings dialog using hotkey
await comfyPage.page.keyboard.press('Control+,')
await comfyPage.page.waitForSelector('.settings-container', {
await comfyPage.page.waitForSelector('[data-testid="settings-dialog"]', {
state: 'visible'
})
@@ -830,7 +840,7 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
// Dialog should be closed
await expect(
comfyPage.page.locator('.settings-container')
comfyPage.page.locator('[data-testid="settings-dialog"]')
).not.toBeVisible()
// Should still be in subgraph

View File

@@ -54,7 +54,10 @@ async function searchAndExpectResult(
test.describe('Subgraph Search Aliases', { tag: ['@subgraph'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
})
test('Can set search aliases on subgraph and find via search', async ({

View File

@@ -0,0 +1,56 @@
import { expect } from '@playwright/test'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
/**
* Tests that templates are automatically fitted to view when loaded.
*
* When openSource === 'template', fitView() is called to ensure
* templates with saved off-screen viewport positions (extra.ds)
* are always displayed correctly.
*/
test.describe('Template Fit View', { tag: ['@canvas', '@workflow'] }, () => {
test('should automatically fit view when loading a template with off-screen saved position', async ({
comfyPage
}) => {
await comfyPage.settings.setSetting('Comfy.EnableWorkflowViewRestore', true)
// Serialize the current default graph, inject an extreme off-screen
// viewport position, then reload it as a template. Without the fix,
// the saved offset [-5000, -5000] would be restored and nodes would
// be invisible.
const viewportState = await comfyPage.page.evaluate(async () => {
const app = window.app!
const workflow = app.graph.serialize()
workflow.extra = {
...workflow.extra,
ds: { scale: 1, offset: [-5000, -5000] }
}
await app.loadGraphData(workflow as ComfyWorkflowJSON, true, true, null, {
openSource: 'template'
})
return {
offsetX: app.canvas.ds.offset[0],
offsetY: app.canvas.ds.offset[1],
nodeCount: app.graph._nodes.length
}
})
expect(viewportState.nodeCount).toBeGreaterThan(0)
// fitView() should have overridden the saved [-5000, -5000] offset
expect(
viewportState.offsetX,
'Viewport X offset should not be the saved off-screen value'
).not.toBe(-5000)
expect(
viewportState.offsetY,
'Viewport Y offset should not be the saved off-screen value'
).not.toBe(-5000)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -22,7 +22,6 @@ test.describe('Settings Search functionality', { tag: '@settings' }, () => {
name: 'TestSettingsExtension',
settings: [
{
// Extensions can register arbitrary setting IDs
id: 'TestHiddenSetting' as TestSettingId,
name: 'Test Hidden Setting',
type: 'hidden',
@@ -30,7 +29,6 @@ test.describe('Settings Search functionality', { tag: '@settings' }, () => {
category: ['Test', 'Hidden']
},
{
// Extensions can register arbitrary setting IDs
id: 'TestDeprecatedSetting' as TestSettingId,
name: 'Test Deprecated Setting',
type: 'text',
@@ -39,7 +37,6 @@ test.describe('Settings Search functionality', { tag: '@settings' }, () => {
category: ['Test', 'Deprecated']
},
{
// Extensions can register arbitrary setting IDs
id: 'TestVisibleSetting' as TestSettingId,
name: 'Test Visible Setting',
type: 'text',
@@ -52,238 +49,143 @@ test.describe('Settings Search functionality', { tag: '@settings' }, () => {
})
test('can open settings dialog and use search box', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Find the search box
const searchBox = comfyPage.page.locator('.settings-search-box input')
await expect(searchBox).toBeVisible()
// Verify search box has the correct placeholder
await expect(searchBox).toHaveAttribute(
await expect(dialog.searchBox).toHaveAttribute(
'placeholder',
expect.stringContaining('Search')
)
})
test('search box is functional and accepts input', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Find and interact with the search box
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('Comfy')
// Verify the input was accepted
await expect(searchBox).toHaveValue('Comfy')
await dialog.searchBox.fill('Comfy')
await expect(dialog.searchBox).toHaveValue('Comfy')
})
test('search box clears properly', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Find and interact with the search box
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('test')
await expect(searchBox).toHaveValue('test')
await dialog.searchBox.fill('test')
await expect(dialog.searchBox).toHaveValue('test')
// Clear the search box
await searchBox.clear()
await expect(searchBox).toHaveValue('')
await dialog.searchBox.clear()
await expect(dialog.searchBox).toHaveValue('')
})
test('settings categories are visible in sidebar', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Check that the sidebar has categories
const categories = comfyPage.page.locator(
'.settings-sidebar .p-listbox-option'
)
expect(await categories.count()).toBeGreaterThan(0)
// Check that at least one category is visible
await expect(categories.first()).toBeVisible()
expect(await dialog.categories.count()).toBeGreaterThan(0)
})
test('can select different categories in sidebar', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Click on a specific category (Appearance) to verify category switching
const appearanceCategory = comfyPage.page.getByRole('option', {
name: 'Appearance'
})
await appearanceCategory.click()
const categoryCount = await dialog.categories.count()
// Verify the category is selected
await expect(appearanceCategory).toHaveClass(/p-listbox-option-selected/)
})
if (categoryCount > 1) {
await dialog.categories.nth(1).click()
test('settings content area is visible', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
// Check that the content area is visible
const contentArea = comfyPage.page.locator('.settings-content')
await expect(contentArea).toBeVisible()
// Check that tab panels are visible
const tabPanels = comfyPage.page.locator('.settings-tab-panels')
await expect(tabPanels).toBeVisible()
await expect(dialog.categories.nth(1)).toHaveClass(
/bg-interface-menu-component-surface-selected/
)
}
})
test('search functionality affects UI state', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Find the search box
const searchBox = comfyPage.page.locator('.settings-search-box input')
// Type in search box
await searchBox.fill('graph')
// Verify that the search input is handled
await expect(searchBox).toHaveValue('graph')
await dialog.searchBox.fill('graph')
await expect(dialog.searchBox).toHaveValue('graph')
})
test('settings dialog can be closed', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Close with escape key
await comfyPage.page.keyboard.press('Escape')
// Verify dialog is closed
await expect(settingsDialog).not.toBeVisible()
await expect(dialog.root).not.toBeVisible()
})
test('search box has proper debouncing behavior', async ({ comfyPage }) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Type rapidly in search box
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('a')
await searchBox.fill('ab')
await searchBox.fill('abc')
await searchBox.fill('abcd')
await dialog.searchBox.fill('a')
await dialog.searchBox.fill('ab')
await dialog.searchBox.fill('abc')
await dialog.searchBox.fill('abcd')
// Verify final value
await expect(searchBox).toHaveValue('abcd')
await expect(dialog.searchBox).toHaveValue('abcd')
})
test('search excludes hidden settings from results', async ({
comfyPage
}) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Search for our test settings
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('Test')
await dialog.searchBox.fill('Test')
// Get all settings content
const settingsContent = comfyPage.page.locator('.settings-tab-panels')
// Should show visible setting but not hidden setting
await expect(settingsContent).toContainText('Test Visible Setting')
await expect(settingsContent).not.toContainText('Test Hidden Setting')
await expect(dialog.contentArea).toContainText('Test Visible Setting')
await expect(dialog.contentArea).not.toContainText('Test Hidden Setting')
})
test('search excludes deprecated settings from results', async ({
comfyPage
}) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Search for our test settings
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('Test')
await dialog.searchBox.fill('Test')
// Get all settings content
const settingsContent = comfyPage.page.locator('.settings-tab-panels')
// Should show visible setting but not deprecated setting
await expect(settingsContent).toContainText('Test Visible Setting')
await expect(settingsContent).not.toContainText('Test Deprecated Setting')
await expect(dialog.contentArea).toContainText('Test Visible Setting')
await expect(dialog.contentArea).not.toContainText(
'Test Deprecated Setting'
)
})
test('search shows visible settings but excludes hidden and deprecated', async ({
comfyPage
}) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
// Search for our test settings
const searchBox = comfyPage.page.locator('.settings-search-box input')
await searchBox.fill('Test')
await dialog.searchBox.fill('Test')
// Get all settings content
const settingsContent = comfyPage.page.locator('.settings-tab-panels')
// Should only show the visible setting
await expect(settingsContent).toContainText('Test Visible Setting')
// Should not show hidden or deprecated settings
await expect(settingsContent).not.toContainText('Test Hidden Setting')
await expect(settingsContent).not.toContainText('Test Deprecated Setting')
await expect(dialog.contentArea).toContainText('Test Visible Setting')
await expect(dialog.contentArea).not.toContainText('Test Hidden Setting')
await expect(dialog.contentArea).not.toContainText(
'Test Deprecated Setting'
)
})
test('search by setting name excludes hidden and deprecated', async ({
comfyPage
}) => {
// Open settings dialog
await comfyPage.page.keyboard.press('Control+,')
const settingsDialog = comfyPage.page.locator('.settings-container')
await expect(settingsDialog).toBeVisible()
const dialog = comfyPage.settingDialog
await dialog.open()
const searchBox = comfyPage.page.locator('.settings-search-box input')
const settingsContent = comfyPage.page.locator('.settings-tab-panels')
await dialog.searchBox.clear()
await dialog.searchBox.fill('Hidden')
await expect(dialog.contentArea).not.toContainText('Test Hidden Setting')
// Search specifically for hidden setting by name
await searchBox.clear()
await searchBox.fill('Hidden')
await dialog.searchBox.clear()
await dialog.searchBox.fill('Deprecated')
await expect(dialog.contentArea).not.toContainText(
'Test Deprecated Setting'
)
// Should not show the hidden setting even when searching by name
await expect(settingsContent).not.toContainText('Test Hidden Setting')
// Search specifically for deprecated setting by name
await searchBox.clear()
await searchBox.fill('Deprecated')
// Should not show the deprecated setting even when searching by name
await expect(settingsContent).not.toContainText('Test Deprecated Setting')
// Search for visible setting by name - should work
await searchBox.clear()
await searchBox.fill('Visible')
// Should show the visible setting
await expect(settingsContent).toContainText('Test Visible Setting')
await dialog.searchBox.clear()
await dialog.searchBox.fill('Visible')
await expect(dialog.contentArea).toContainText('Test Visible Setting')
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -102,6 +102,7 @@ test.describe('Vue Node Link Interaction', { tag: '@screenshot' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
// await comfyPage.setup()
await comfyPage.workflow.loadWorkflow('vueNodes/simple-triple')
await comfyPage.vueNodes.waitForNodes()
@@ -928,7 +929,10 @@ test.describe('Vue Node Link Interaction', { tag: '@screenshot' }, () => {
'Comfy.LinkRelease.ActionShift',
'context menu'
)
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
const samplerNode = (
await comfyPage.nodeOps.getNodeRefsByType('KSampler')
@@ -994,6 +998,10 @@ test.describe('Vue Node Link Interaction', { tag: '@screenshot' }, () => {
'Comfy.LinkRelease.ActionShift',
'search box'
)
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
const samplerNode = (
await comfyPage.nodeOps.getNodeRefsByType('KSampler')
@@ -1048,6 +1056,11 @@ test.describe('Vue Node Link Interaction', { tag: '@screenshot' }, () => {
comfyPage,
comfyMouse
}) => {
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
// Setup workflow with a KSampler node
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
await comfyPage.nodeOps.waitForGraphNodes(0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,58 @@
import { expect } from '@playwright/test'
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
import { comfyPageFixture as test } from '../../../../fixtures/ComfyPage'
test.describe('Vue Nodes Image Preview', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
await comfyPage.vueNodes.waitForNodes()
})
async function loadImageOnNode(comfyPage: ComfyPage) {
const loadImageNode = (
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
)[0]
const { x, y } = await loadImageNode.getPosition()
await comfyPage.dragDrop.dragAndDropFile('image64x64.webp', {
dropPosition: { x, y }
})
const imagePreview = comfyPage.page.locator('.image-preview')
await expect(imagePreview).toBeVisible()
await expect(imagePreview.locator('img')).toBeVisible()
await expect(imagePreview).toContainText('x')
return imagePreview
}
// TODO(#8143): Re-enable after image preview sync is working in CI
test.fixme('opens mask editor from image preview button', async ({
comfyPage
}) => {
const imagePreview = await loadImageOnNode(comfyPage)
await imagePreview.locator('[role="img"]').hover()
await comfyPage.page.getByLabel('Edit or mask image').click()
await expect(comfyPage.page.locator('.mask-editor-dialog')).toBeVisible()
})
// TODO(#8143): Re-enable after image preview sync is working in CI
test.fixme('shows image context menu options', async ({ comfyPage }) => {
await loadImageOnNode(comfyPage)
const nodeHeader = comfyPage.vueNodes.getNodeByTitle('Load Image')
await nodeHeader.click()
await nodeHeader.click({ button: 'right' })
const contextMenu = comfyPage.page.locator('.p-contextmenu')
await expect(contextMenu).toBeVisible()
await expect(contextMenu.getByText('Open Image')).toBeVisible()
await expect(contextMenu.getByText('Copy Image')).toBeVisible()
await expect(contextMenu.getByText('Save Image')).toBeVisible()
await expect(contextMenu.getByText('Open in Mask Editor')).toBeVisible()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 80 KiB

32
global.d.ts vendored
View File

@@ -5,9 +5,33 @@ declare const __ALGOLIA_APP_ID__: string
declare const __ALGOLIA_API_KEY__: string
declare const __USE_PROD_CONFIG__: boolean
interface ImpactQueueFunction {
(...args: unknown[]): void
a?: unknown[][]
}
type GtagGetFieldName = 'client_id' | 'session_id' | 'session_number'
interface GtagGetFieldValueMap {
client_id: string | number | undefined
session_id: string | number | undefined
session_number: string | number | undefined
}
interface GtagFunction {
<TField extends GtagGetFieldName>(
command: 'get',
targetId: string,
fieldName: TField,
callback: (value: GtagGetFieldValueMap[TField]) => void
): void
(...args: unknown[]): void
}
interface Window {
__CONFIG__: {
gtm_container_id?: string
ga_measurement_id?: string
mixpanel_token?: string
require_whitelist?: boolean
subscription_required?: boolean
@@ -31,12 +55,10 @@ interface Window {
badge?: string
}
}
__ga_identity__?: {
client_id?: string
session_id?: string
session_number?: string
}
dataLayer?: Array<Record<string, unknown>>
gtag?: GtagFunction
ire_o?: string
ire?: ImpactQueueFunction
}
interface Navigator {

View File

@@ -35,18 +35,6 @@
background-size: cover;
background-repeat: no-repeat;
}
#vue-app:has(#loading-logo) {
display: contents;
color: var(--fg-color);
& #loading-logo {
place-self: center;
font-size: clamp(2px, 1vw, 6px);
line-height: 1;
overflow: hidden;
max-width: 100vw;
border-radius: 20ch;
}
}
.visually-hidden {
position: absolute;
width: 1px;
@@ -65,36 +53,6 @@
<body class="litegraph grid">
<div id="vue-app">
<span class="visually-hidden" role="status">Loading ComfyUI...</span>
<svg
width="520"
height="520"
viewBox="0 0 520 520"
fill="none"
xmlns="http://www.w3.org/2000/svg"
id="loading-logo"
>
<mask
id="mask0_227_285"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="520"
height="520"
>
<path
d="M0 184.335C0 119.812 0 87.5502 12.5571 62.9055C23.6026 41.2274 41.2274 23.6026 62.9055 12.5571C87.5502 0 119.812 0 184.335 0H335.665C400.188 0 432.45 0 457.094 12.5571C478.773 23.6026 496.397 41.2274 507.443 62.9055C520 87.5502 520 119.812 520 184.335V335.665C520 400.188 520 432.45 507.443 457.094C496.397 478.773 478.773 496.397 457.094 507.443C432.45 520 400.188 520 335.665 520H184.335C119.812 520 87.5502 520 62.9055 507.443C41.2274 496.397 23.6026 478.773 12.5571 457.094C0 432.45 0 400.188 0 335.665V184.335Z"
fill="#EEFF30"
/>
</mask>
<g mask="url(#mask0_227_285)">
<rect y="0.751831" width="520" height="520" fill="#172DD7" />
<path
d="M176.484 428.831C168.649 428.831 162.327 425.919 158.204 420.412C153.966 414.755 152.861 406.857 155.171 398.749L164.447 366.178C165.187 363.585 164.672 360.794 163.059 358.636C161.446 356.483 158.921 355.216 156.241 355.216H129.571C121.731 355.216 115.409 352.308 111.289 346.802C107.051 341.14 105.946 333.242 108.258 325.134L140.124 213.748L143.642 201.51C148.371 184.904 165.62 171.407 182.097 171.407H214.009C217.817 171.407 221.167 168.868 222.215 165.183L232.769 128.135C237.494 111.545 254.742 98.048 271.219 98.048L339.468 97.9264L389.431 97.9221C397.268 97.9221 403.59 100.831 407.711 106.337C411.949 111.994 413.054 119.892 410.744 128L396.457 178.164C391.734 194.75 374.485 208.242 358.009 208.242L289.607 208.372H257.706C253.902 208.372 250.557 210.907 249.502 214.588L222.903 307.495C222.159 310.093 222.673 312.892 224.291 315.049C225.904 317.202 228.428 318.469 231.107 318.469C231.113 318.469 276.307 318.381 276.307 318.381H326.122C333.959 318.381 340.281 321.29 344.402 326.796C348.639 332.457 349.744 340.355 347.433 348.463L333.146 398.619C328.423 415.209 311.174 428.701 294.698 428.701L226.299 428.831H176.484Z"
fill="#F0FF41"
/>
</g>
</svg>
</div>
<script type="module" src="src/main.ts"></script>
</body>

View File

@@ -64,7 +64,8 @@ const config: KnipConfig = {
},
tags: [
'-knipIgnoreUnusedButUsedByCustomNodes',
'-knipIgnoreUnusedButUsedByVueNodesBranch'
'-knipIgnoreUnusedButUsedByVueNodesBranch',
'-knipIgnoreUsedByStackedPR'
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.39.10",
"version": "1.41.1",
"private": true,
"description": "Official front-end implementation of ComfyUI",
"homepage": "https://comfy.org",
@@ -70,13 +70,14 @@
"@primevue/themes": "catalog:",
"@sentry/vue": "catalog:",
"@sparkjsdev/spark": "catalog:",
"@tiptap/core": "^2.10.4",
"@tiptap/extension-link": "^2.10.4",
"@tiptap/extension-table": "^2.10.4",
"@tiptap/extension-table-cell": "^2.10.4",
"@tiptap/extension-table-header": "^2.10.4",
"@tiptap/extension-table-row": "^2.10.4",
"@tiptap/starter-kit": "^2.10.4",
"@tiptap/core": "catalog:",
"@tiptap/extension-link": "catalog:",
"@tiptap/extension-table": "catalog:",
"@tiptap/extension-table-cell": "catalog:",
"@tiptap/extension-table-header": "catalog:",
"@tiptap/extension-table-row": "catalog:",
"@tiptap/pm": "catalog:",
"@tiptap/starter-kit": "catalog:",
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"@xterm/addon-fit": "^0.10.0",
@@ -93,9 +94,9 @@
"extendable-media-recorder-wav-encoder": "^7.0.129",
"firebase": "catalog:",
"fuse.js": "^7.0.0",
"glob": "^11.0.3",
"glob": "catalog:",
"jsonata": "catalog:",
"jsondiffpatch": "^0.6.0",
"jsondiffpatch": "catalog:",
"loglevel": "^1.9.2",
"marked": "^15.0.11",
"pinia": "catalog:",
@@ -193,7 +194,7 @@
},
"pnpm": {
"overrides": {
"vite": "^8.0.0-beta.8"
"vite": "catalog:"
}
}
}

View File

@@ -12,6 +12,12 @@
icon-sets: from-folder(comfy, './packages/design-system/src/icons');
}
/* Safelist dynamic comfy icons for node library folders */
@source inline("icon-[comfy--{ai-model,bfl,bria,bytedance,credits,extensions-blocks,file-output,gemini,grok,hitpaw,ideogram,image-ai-edit,kling,ltxv,luma,magnific,mask,meshy,minimax,moonvalley-marey,node,openai,pin,pixverse,play,recraft,rodin,runway,sora,stability-ai,template,tencent,topaz,tripo,veo,vidu,wan,wavespeed,workflow}]");
/* Safelist dynamic comfy icons for essential nodes (kebab-case of node names) */
@source inline("icon-[comfy--{load-image,save-image,load-video,save-video,load-3-d,save-glb,image-batch,image-crop,image-scale,image-rotate,image-blur,image-invert,canny,recraft-remove-background-node,kling-lip-sync-audio-to-video-node,load-audio,save-audio,stability-text-to-audio,lora-loader,clip-text-encode,get-video-components,tencent-text-to-model-node,tencent-image-to-model-node,open-ai-chat-node,subgraph-blueprint-canny-to-video-ltx-2-0,subgraph-blueprint-pose-to-video-ltx-2-0}]");
@custom-variant touch (@media (hover: none));
@theme {
@@ -1186,7 +1192,7 @@ button.comfy-queue-btn {
.graphdialog {
min-height: 1em;
background-color: var(--comfy-menu-bg);
z-index: 41; /* z-index is set to 41 here in order to appear over selection-overlay-container which should have a z-index of 40 */
z-index: 1500;
}
.graphdialog .name {

View File

@@ -0,0 +1,10 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12672)">
<path d="M365.047 229.802H310.584L256.118 153.072L86.2157 392.168H140.796L256.115 229.807H310.581L195.261 392.168H249.994L365.047 229.802L512 436.698H470.893V436.7H426.019V392.343L365.047 306.532L304.415 392.178V436.698H163.632L163.629 436.703H109.164L109.167 436.698H-0.105347L256.118 76L365.047 229.802Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1471_12672">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View File

@@ -0,0 +1,18 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12667)">
<g clip-path="url(#clip1_1471_12667)">
<path d="M0.307203 236.902C1.1008 227.43 4.48 211.021 6.5792 201.139C23.5008 121.805 83.0208 46.1824 160.691 20.3776V389.734H411.443C419.84 389.734 446.259 381.158 455.168 377.446C468.685 371.814 480.691 363.648 491.597 354.074C465.357 424.038 401.024 480.896 329.242 501.094C314.01 505.37 290.611 510.694 275.226 512H239.59C223.667 508.16 207.155 507.136 191.206 503.117C103.936 481.152 29.7984 406.63 8.704 318.925C5.0176 303.59 4.0704 287.744 0.307203 272.538C1.024 260.89 -0.665597 248.397 0.307203 236.877V236.902Z" fill="#8E4CFF"/>
<path d="M265.062 211.43H375.808C378.394 211.43 392.55 217.498 395.75 219.494C427.213 238.925 420.122 291.226 384.794 301.952C382.771 302.566 366.669 305.69 365.619 305.69H268.877L265.062 301.875V211.456V211.43Z" fill="#8E4CFF"/>
<path d="M265.062 137.549V48.4096H373.248C374.861 48.4096 384.205 53.8624 386.611 55.424C406.528 68.3776 414.49 96.5376 401.152 117.069C398.106 121.754 380.339 137.574 375.808 137.574H265.062V137.549Z" fill="#8E4CFF"/>
<path d="M489.062 147.738C496.128 167.706 505.523 187.264 506.906 208.845L491.674 192.282C477.773 180.736 460.928 174.003 443.29 170.624L489.062 147.738Z" fill="#8E4CFF"/>
</g>
</g>
<defs>
<clipPath id="clip0_1471_12667">
<rect width="512" height="512" fill="white"/>
</clipPath>
<clipPath id="clip1_1471_12667">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,13 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12658)">
<path d="M324.094 389.858L284.667 379.567V191.5L326.871 180.816C350.01 174.941 369.446 170.154 370.371 170.339C371.112 170.339 371.667 222.027 371.667 285.326V400.334L367.594 400.15C365.189 400.15 345.566 395.361 324.094 389.835V389.857V389.858Z" fill="#00C8D2"/>
<path d="M138.667 343.325C138.667 279.602 139.229 227.339 140.166 227.339C140.914 227.154 160.573 231.998 184.164 237.913L226.667 248.65L226.292 342.975L225.73 437.278L187.535 447.107C166.565 452.463 146.906 457.47 144.097 458.029L138.667 459.334V343.325Z" fill="#3C8CFF"/>
<path d="M423.667 248.299C423.667 38.7081 423.853 27.4506 427.037 28.3797C428.722 28.9368 445.386 33.1843 463.921 37.8029C482.458 42.6075 500.807 47.2031 504.739 48.1312L511.667 49.9884L511.293 248.67L510.731 447.539L472.722 457.148C451.939 462.486 432.279 467.291 429.284 468.057L423.667 469.334V248.299Z" fill="#78E6DC"/>
<path d="M-0.333038 248.845C-0.333038 140.208 0.222275 51.334 1.14852 51.334C1.88822 51.334 21.3242 56.1412 44.4631 61.8769L86.667 72.583V248.66C86.667 345.267 86.296 424.55 85.9262 424.55C85.3709 424.55 65.7494 429.544 42.4262 435.466L-0.333038 446.334V248.823V248.844V248.845Z" fill="#325AB4"/>
</g>
<defs>
<clipPath id="clip0_1471_12658">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34 3.11111V6.07407C34 6.68795 33.4163 7.18519 32.6956 7.18519C31.975 7.18519 31.3913 6.68795 31.3913 6.07407V4.22222H25.3042V19.7778H28.3479C29.0685 19.7778 29.6523 20.275 29.6523 20.8889C29.6523 21.5028 29.0685 22 28.3479 22H19.6521C18.9315 22 18.3477 21.5028 18.3477 20.8889C18.3477 20.275 18.9315 19.7778 19.6521 19.7778H22.6958V4.22222H16.6087V6.07407C16.6087 6.68795 16.025 7.18519 15.3044 7.18519C14.5837 7.18519 14 6.68795 14 6.07407V3.11111C14 2.49723 14.5837 2 15.3044 2H32.6959C33.4166 2 34 2.49723 34 3.11111Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 9">
<path d="M1.82148 8.68376C1.61587 8.68376 1.44996 8.60733 1.34177 8.46284C1.23057 8.31438 1.20157 8.10711 1.26219 7.89434L1.50561 7.03961C1.52502 6.97155 1.51151 6.89831 1.46918 6.8417C1.42684 6.7852 1.3606 6.75194 1.29025 6.75194H0.590376C0.384656 6.75194 0.21875 6.67562 0.110614 6.53113C-0.000591531 6.38256 -0.0295831 6.17529 0.0310774 5.96252L0.867308 3.03952L0.959638 2.71838C1.08375 2.28258 1.53638 1.9284 1.96878 1.9284H2.80622C2.90615 1.9284 2.99406 1.86177 3.02157 1.76508L3.29852 0.79284C3.4225 0.357484 3.87514 0.0033043 4.30753 0.0033043L6.09854 0.000112775L7.40967 0C7.61533 0 7.78124 0.0763259 7.88937 0.220813C8.00058 0.369269 8.02957 0.576538 7.96895 0.78931L7.59405 2.10572C7.4701 2.54096 7.01746 2.89503 6.58507 2.89503L4.79008 2.89844H3.95292C3.8531 2.89844 3.7653 2.96496 3.73762 3.06155L3.03961 5.49964C3.02008 5.56781 3.03359 5.64127 3.07604 5.69787C3.11837 5.75437 3.18461 5.78763 3.2549 5.78763C3.25507 5.78763 4.44105 5.78532 4.44105 5.78532H5.7483C5.95396 5.78532 6.11986 5.86164 6.228 6.00613C6.33921 6.1547 6.3682 6.36197 6.30754 6.57474L5.93263 7.89092C5.80869 8.32628 5.35605 8.68034 4.92366 8.68034L3.12872 8.68376H1.82148Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.9316 2.13269H25.2057V3.57269H29.9316C31.1241 3.57269 32.0916 4.54018 32.0916 5.73269V18.0646C32.0916 19.2571 31.1241 20.2246 29.9316 20.2246H25.2057V21.6646H29.9316C30.8859 21.6646 31.8019 21.2849 32.4768 20.6099C33.1516 19.9349 33.5314 19.019 33.5314 18.0647V5.73281C33.5314 4.77843 33.1518 3.86249 32.4768 3.18761C31.8018 2.51273 30.8858 2.13293 29.9316 2.13293V2.13269Z" fill="#8A8A8A"/>
<path d="M30.2025 15.7602C30.5531 15.7602 30.8738 15.5652 31.0341 15.2539C31.1953 14.9427 31.1691 14.5677 30.9656 14.2817L29.0269 11.5601L26.7994 8.44014C26.6231 8.19359 26.3391 8.04733 26.0363 8.04733C25.7335 8.04733 25.4494 8.19358 25.2731 8.44014L25.2056 8.53389V15.7601L30.2025 15.7602Z" fill="#8A8A8A"/>
<path d="M23.0457 1.00018C22.6482 1.00018 22.3257 1.32269 22.3257 1.72018V2.13269H17.5999C16.6455 2.13269 15.7296 2.51237 15.0547 3.18737C14.3798 3.86237 14 4.77831 14 5.73257V18.0645C14 19.0189 14.3797 19.9348 15.0547 20.6097C15.7297 21.2846 16.6456 21.6644 17.5999 21.6644H22.3257V21.88C22.3257 22.2775 22.6482 22.6 23.0457 22.6C23.4432 22.6 23.7657 22.2775 23.7657 21.88V1.72C23.7657 1.32251 23.4432 1.00018 23.0457 1.00018ZM22.3257 15.7602H17.3289C16.9783 15.7602 16.6577 15.5652 16.4974 15.2539C16.3361 14.9427 16.3624 14.5677 16.5658 14.2817L17.4471 13.0433L18.6208 11.397H18.6199C18.7961 11.1495 19.0802 11.0033 19.383 11.0033C19.6867 11.0033 19.9708 11.1495 20.1471 11.397L21.318 13.0433L21.6517 13.5233L22.3258 12.568L22.3257 15.7602Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,5 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.9316 2.13269H25.2057V3.57269H29.9316C31.1241 3.57269 32.0916 4.54018 32.0916 5.73269V18.0646C32.0916 19.2571 31.1241 20.2246 29.9316 20.2246H25.2057V21.6646H29.9316C30.8859 21.6646 31.8019 21.2849 32.4768 20.6099C33.1516 19.9349 33.5314 19.019 33.5314 18.0647V5.73281C33.5314 4.77843 33.1518 3.86249 32.4768 3.18761C31.8018 2.51273 30.8858 2.13293 29.9316 2.13293V2.13269Z" fill="#8A8A8A"/>
<path d="M29.0586 10.918C29.5299 11.2086 29.703 11.7469 29.7031 12.1836C29.7031 12.6202 29.5288 13.1584 29.0576 13.4492L25 16.0273V12.4717L25.6396 12.0801L25 11.6865V8.33887L29.0586 10.918Z" fill="#8A8A8A"/>
<path d="M23.0459 1C23.4433 1.0001 23.7656 1.32234 23.7656 1.71973V21.8799C23.7656 22.2773 23.4433 22.5995 23.0459 22.5996C22.6484 22.5996 22.3262 22.2774 22.3262 21.8799V21.6641H17.5996C16.6454 21.664 15.7296 21.2842 15.0547 20.6094C14.3798 19.9345 14 19.0188 14 18.0645V5.73242C14 4.77822 14.3799 3.86249 15.0547 3.1875C15.7295 2.51256 16.6453 2.13288 17.5996 2.13281H22.3262V1.71973C22.3263 1.32236 22.6485 1 23.0459 1ZM19.3008 5.00195C18.5469 5.04101 17.991 5.7594 18.001 6.48438V17.8672C17.9877 18.3783 18.241 18.8887 18.667 19.1621L18.6709 19.165C19.1025 19.4368 19.6599 19.4326 20.0879 19.1494L22 17.9336V14.3105L20.7266 15.0918V9.06055L22 9.84277V6.43359L20.0869 5.21875C19.8834 5.08395 19.6474 5.00777 19.4072 5L19.3008 5.00195Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0zm-13.239 0a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0zm6.619 6.619a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0zm0-13.238a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 665 B

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3697 9.5C22.4578 9.50289 22.5441 9.53066 22.6187 9.58008L25.9107 11.6699C26.0837 11.7765 26.1471 11.9746 26.1471 12.1348C26.147 12.2949 26.0827 12.4921 25.9098 12.5986L22.6187 14.6895C22.4617 14.7931 22.2574 14.7941 22.0992 14.6943L22.0982 14.6934C21.9419 14.5931 21.8483 14.4063 21.8531 14.2188V10.0439C21.8496 9.77812 22.0541 9.51424 22.3307 9.5H22.3697ZM22.8619 13.2754L24.6646 12.1338L22.8619 10.9893V13.2754Z" fill="#8A8A8A"/>
<path d="M18 1.2002C18.4418 1.2002 18.7998 1.55817 18.7998 2V5.2002H29C29.9941 5.2002 30.7998 6.00589 30.7998 7V17.7002H34C34.4418 17.7002 34.7998 18.0582 34.7998 18.5C34.7998 18.9418 34.4418 19.2998 34 19.2998H30.7998V22.5C30.7998 22.9418 30.4418 23.2998 30 23.2998C29.5582 23.2998 29.2002 22.9418 29.2002 22.5V19.2998H19C18.0059 19.2998 17.2002 18.4941 17.2002 17.5V6.7998H14C13.5582 6.7998 13.2002 6.44183 13.2002 6C13.2002 5.55817 13.5582 5.2002 14 5.2002H17.2002V2C17.2002 1.55817 17.5582 1.2002 18 1.2002ZM18.7998 17.5C18.7998 17.6105 18.8895 17.7002 19 17.7002H29.2002V7C29.2002 6.88954 29.1105 6.79981 29 6.7998H18.7998V17.5Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,12 @@
<svg width="49" height="24" viewBox="0 0 49 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.2461 5C47.9942 5.00009 48.6152 5.59922 48.6152 6.32031V16.8799C48.6152 17.601 47.9942 18.2001 47.2461 18.2002H31.9844C31.2363 18.2 30.6152 17.6009 30.6152 16.8799V6.32031C30.6152 5.59931 31.2363 5.00024 31.9844 5H47.2461ZM31.7891 14.918V16.8799C31.7891 16.9939 31.8661 17.0682 31.9844 17.0684H47.2461C47.3644 17.0682 47.4414 16.994 47.4414 16.8799V15.2656L44.085 12.6787L41.3154 14.5166C41.1091 14.6507 40.8115 14.6383 40.6182 14.4873L36.8516 11.5527L31.7891 14.918ZM31.9844 6.13184C31.8662 6.13202 31.7891 6.20628 31.7891 6.32031V13.5391L36.54 10.3799C36.6194 10.3255 36.7125 10.2913 36.8086 10.2803C36.9629 10.2641 37.1232 10.3091 37.2432 10.4033L41.0098 13.3447L43.7852 11.5059C43.9915 11.3718 44.2891 11.3841 44.4824 11.5352L47.4414 13.8154V6.32031C47.4414 6.20619 47.3645 6.13191 47.2461 6.13184H31.9844ZM40.9854 7.63965C41.9503 7.63986 42.7459 8.40684 42.7461 9.33691C42.7461 10.2671 41.9505 11.034 40.9854 11.0342C40.0201 11.0342 39.2236 10.2673 39.2236 9.33691C39.2238 8.40671 40.0202 7.63965 40.9854 7.63965ZM40.9854 8.77148C40.6545 8.77148 40.3986 9.01812 40.3984 9.33691C40.3984 9.65587 40.6544 9.90332 40.9854 9.90332C41.3161 9.90312 41.5723 9.65574 41.5723 9.33691C41.5721 9.01825 41.316 8.77168 40.9854 8.77148Z" fill="#8A8A8A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.6398 11.7209C27.8739 11.9552 27.874 12.3353 27.6398 12.5695L25.0948 15.1154C24.8607 15.3496 24.4805 15.3492 24.2462 15.1154C24.0119 14.8811 24.0119 14.5011 24.2462 14.2668L25.7638 12.7492L18.2159 12.7492C17.8845 12.7492 17.6153 12.481 17.6153 12.1496C17.6153 11.8182 17.8846 11.55 18.2159 11.55L25.7726 11.55L24.2462 10.0236C24.0119 9.78931 24.0119 9.40931 24.2462 9.175C24.4805 8.94094 24.8606 8.94077 25.0948 9.175L27.6398 11.7209Z" fill="#8A8A8A"/>
<rect x="0.5" y="4.5" width="14" height="14" rx="2.5" fill="#1C1C24" stroke="#8A8A8A"/>
<circle cx="9.04375" cy="10.6062" r="3.98125" fill="url(#paint0_radial_1684_13636)"/>
<defs>
<radialGradient id="paint0_radial_1684_13636" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.53125 10.7687) rotate(-139.289) scale(4.6091)">
<stop offset="0.00961538" stop-color="white"/>
<stop offset="1" stop-color="#686868"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,12 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0245 11.721C28.2587 11.9553 28.2588 12.3354 28.0245 12.5696L25.4796 15.1155C25.2454 15.3497 24.8653 15.3494 24.631 15.1155C24.3967 14.8812 24.3967 14.5012 24.631 14.2669L26.1485 12.7493L18.6007 12.7493C18.2693 12.7493 18.0001 12.4811 18.0001 12.1497C18.0001 11.8184 18.2693 11.5501 18.6007 11.5501L26.1573 11.5501L24.631 10.0238C24.3966 9.78943 24.3966 9.40944 24.631 9.17512C24.8653 8.94106 25.2454 8.94089 25.4796 9.17512L28.0245 11.721Z" fill="#8A8A8A"/>
<rect x="0.5" y="4.61523" width="14" height="14" rx="2.5" fill="#1C1C24" stroke="#8A8A8A"/>
<circle cx="9.04375" cy="10.7215" r="3.98125" fill="url(#paint0_radial_1694_13931)"/>
<path d="M44.203 5C46.2974 5 48.01 6.71671 48.01 8.84956V14.3804C48.01 16.5133 46.2974 18.23 44.203 18.23H34.807C32.7125 18.23 31 16.5133 31 14.3804V8.84956C31 6.71671 32.7125 5 34.807 5H44.203ZM34.807 6.37812C33.4315 6.37812 32.3499 7.48086 32.3499 8.84956V14.3804C32.3499 15.7491 33.4315 16.8519 34.807 16.8519H44.203C45.5785 16.8519 46.6601 15.7491 46.6601 14.3804V8.84956C46.6601 7.48086 45.5785 6.37812 44.203 6.37812H34.807ZM37.5598 8.12949C37.6752 8.13322 37.7884 8.16994 37.8862 8.23544L42.193 11.0009C42.4194 11.1419 42.5025 11.403 42.5025 11.615C42.5025 11.8269 42.419 12.0878 42.1927 12.2288L37.8865 14.9946C37.6809 15.132 37.4135 15.1341 37.2063 15.002L37.2046 15.0009C37 14.8683 36.8785 14.6208 36.8848 14.3727V8.84956C36.88 8.49772 37.1469 8.14891 37.5089 8.13007L37.5598 8.12949ZM38.2041 13.1249L40.5626 11.6144L38.2041 10.0996V13.1249ZM42.1753 11.8255C42.1606 11.8574 42.1424 11.887 42.1205 11.913L42.1506 11.8717C42.1598 11.8571 42.168 11.8414 42.1753 11.8255Z" fill="#8A8A8A"/>
<defs>
<radialGradient id="paint0_radial_1694_13931" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.53125 10.884) rotate(-139.289) scale(4.6091)">
<stop offset="0.00961538" stop-color="white"/>
<stop offset="1" stop-color="#686868"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M32.2435 1.33301C33.4808 1.33312 34.4935 2.34575 34.4935 3.58301V11.2422C35.9784 11.809 36.5649 13.8317 35.3109 15.0859C28.4415 21.9551 28.9659 21.498 28.681 21.5654C25.4267 22.3753 25.6288 22.3379 25.5013 22.3379L25.4935 22.3301C25.0063 22.3298 24.647 21.8727 24.767 21.4004C25.5693 18.2061 25.5088 18.2582 25.7113 18.0557L30.7767 12.9893C30.2817 12.6244 29.582 12.1127 28.6029 11.4082L24.5423 14.8135C24.2463 15.0618 23.7681 15.0618 23.472 14.8135L17.1341 9.49805L13.4955 12.042V17.0811C13.4955 17.4933 13.8322 17.8308 14.2445 17.8311H23.2445C23.6568 17.8313 23.9945 18.1687 23.9945 18.5811C23.9943 18.9933 23.6567 19.3309 23.2445 19.3311H14.2445C13.0073 19.3308 11.9955 18.3182 11.9955 17.0811V3.58301C11.9955 2.34583 13.0073 1.33325 14.2445 1.33301H32.2435ZM26.9183 18.9629L26.5209 20.5459L28.1039 20.1484L32.2982 15.9521C32.0571 15.7222 31.6993 15.3563 31.1127 14.7676L26.9183 18.9629ZM34.4857 13.4219C34.4857 12.672 33.5781 12.3043 33.0531 12.8291L32.7962 13.085C32.7438 13.1746 32.6636 13.2544 32.5629 13.3184L32.1712 13.71L33.3558 14.8945L34.2377 14.0137C34.3951 13.8562 34.4856 13.6468 34.4857 13.4219ZM14.2445 2.83301C13.8322 2.83325 13.4955 3.1707 13.4955 3.58301V10.4395L16.6937 8.14844C16.9973 7.93835 17.4383 7.951 17.7191 8.18652L24.0111 13.4639L28.0267 10.0967C28.3076 9.86126 28.7554 9.84805 29.0589 10.0645L31.8558 11.9102L31.9955 11.7715C32.2782 11.4887 32.6198 11.2892 32.9935 11.1816V3.58301C32.9935 3.17062 32.6559 2.83312 32.2435 2.83301H14.2445ZM24.2494 4.33301C25.4866 4.33301 26.4991 5.3449 26.4994 6.58203C26.4994 7.81936 25.4868 8.83203 24.2494 8.83203C23.0122 8.8318 22.0004 7.81922 22.0004 6.58203C22.0006 5.34504 23.0123 4.33323 24.2494 4.33301ZM24.2494 5.83301C23.8372 5.83323 23.4996 6.16991 23.4994 6.58203C23.4994 6.99435 23.8371 7.3318 24.2494 7.33203C24.6618 7.33203 24.9994 6.99449 24.9994 6.58203C24.9991 6.16977 24.6617 5.83301 24.2494 5.83301Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.6289 0C30.5839 0.000217009 32.9999 2.46547 33 5.52832V11.666L31.0957 13.3594V5.52832C31.0956 3.56289 29.5694 1.9797 27.6289 1.97949H14.3711C12.4306 1.9797 10.9044 3.56289 10.9043 5.52832V13.4717C10.9044 15.4371 12.4306 17.0203 14.3711 17.0205H27V19H14.3711C11.4161 18.9998 9.00008 16.5345 9 13.4717V5.52832C9.00008 2.46547 11.4161 0.000217423 14.3711 0H27.6289ZM18.2559 4.49414C18.4185 4.49956 18.578 4.55256 18.7158 4.64648L24.793 8.61816C25.1122 8.82076 25.2295 9.19571 25.2295 9.5C25.2295 9.80434 25.1113 10.1792 24.792 10.3818L18.7168 14.3535C18.4268 14.5509 18.0492 14.5538 17.7568 14.3643L17.7539 14.3623C17.4655 14.1718 17.2938 13.8161 17.3027 13.46V5.52832C17.296 5.02308 17.6729 4.52218 18.1836 4.49512L18.2559 4.49414ZM19.1641 11.668L22.4922 9.49902L19.1641 7.32422V11.668Z" fill="#8A8A8A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.784 8.33301C40.5601 8.37159 41.2873 8.69547 41.8368 9.24316L41.8407 9.24707C42.4245 9.83307 42.7546 10.6211 42.7547 11.4551C42.7547 11.8685 42.6746 12.2762 42.5155 12.6572C42.3585 13.0341 42.1305 13.3744 41.8446 13.6631L41.8397 13.667L41.1717 14.3369C41.0982 14.4106 41.0176 14.4759 40.9325 14.5332C40.8753 14.6183 40.8098 14.6989 40.7362 14.7725L33.4803 22.0264C33.2633 22.2432 32.9882 22.3939 32.6883 22.459L29.9276 23.0576C29.396 23.1727 28.8415 23.0106 28.4569 22.626C28.0723 22.2413 27.91 21.6869 28.0252 21.1553L28.6239 18.3945L28.6522 18.2832C28.7275 18.0268 28.8667 17.7924 29.0565 17.6025L36.3124 10.3477L36.4335 10.2383C36.4711 10.2075 36.51 10.1782 36.5497 10.1514C36.6059 10.0679 36.6713 9.98891 36.745 9.91504L37.4139 9.24609L37.4188 9.24023C38.0053 8.65788 38.7927 8.3291 39.6278 8.3291L39.784 8.33301ZM30.1874 18.7344L29.5887 21.4941L32.3485 20.8955L38.9071 14.3369L36.745 12.1758L30.1874 18.7344ZM39.6278 9.92871C39.2325 9.92871 38.8609 10.078 38.5751 10.3477L40.7333 12.5059C40.863 12.3681 40.9676 12.2125 41.0389 12.041C41.0962 11.9038 41.1326 11.758 41.1473 11.6074L41.1551 11.4551C41.155 11.0484 40.9947 10.6649 40.7069 10.376C40.418 10.0883 40.0349 9.92882 39.6278 9.92871Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.6635 4.83333L30.6632 7.83286M16.9985 5.49937V9.49874M30.9971 13.4981V17.4975M21.998 1.5V3.49969M18.9983 7.49906H14.9987M32.9969 15.4978H28.9973M22.9979 2.49984H20.9981M33.6369 3.13979L32.3571 1.85999C32.2446 1.74632 32.1106 1.65609 31.963 1.59451C31.8154 1.53293 31.6571 1.50122 31.4971 1.50122C31.3372 1.50122 31.1789 1.53293 31.0313 1.59451C30.8837 1.65609 30.7497 1.74632 30.6372 1.85999L14.3588 18.1374C14.2451 18.2499 14.1549 18.3838 14.0933 18.5314C14.0317 18.679 14 18.8374 14 18.9973C14 19.1572 14.0317 19.3156 14.0933 19.4631C14.1549 19.6107 14.2451 19.7447 14.3588 19.8572L15.6387 21.137C15.7505 21.2518 15.8842 21.3432 16.0319 21.4055C16.1796 21.4679 16.3383 21.5 16.4986 21.5C16.6589 21.5 16.8176 21.4679 16.9653 21.4055C17.113 21.3432 17.2467 21.2518 17.3585 21.137L33.6369 4.85952C33.7518 4.74771 33.8432 4.61402 33.9055 4.46633C33.9679 4.31865 34 4.15996 34 3.99965C34 3.83934 33.9679 3.68066 33.9055 3.53297C33.8432 3.38528 33.7518 3.25159 33.6369 3.13979Z" stroke="#8A8A8A" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.7844 13.8522L29.9803 15.6814M24.2027 8.42231V10.8612M26.6082 16.2013V18.6402M31.7844 8V9.21945M23 9.64176H25.4055M25.4055 17.4207H27.8109M31.183 8.60973H32.3857M27.1899 11.8036L27.9597 11.0231C28.0273 10.9538 28.1079 10.8988 28.1966 10.8612C28.2854 10.8237 28.3807 10.8043 28.4768 10.8043C28.573 10.8043 28.6683 10.8237 28.757 10.8612C28.8458 10.8988 28.9263 10.9538 28.994 11.0231L38.7842 20.9494C38.8526 21.018 38.9069 21.0997 38.9439 21.1897C38.9809 21.2797 39 21.3763 39 21.4738C39 21.5713 38.9809 21.6679 38.9439 21.7579C38.9069 21.8479 38.8526 21.9296 38.7842 21.9982L38.0145 22.7786C37.9472 22.8487 37.8668 22.9044 37.778 22.9424C37.6892 22.9804 37.5937 23 37.4973 23C37.4009 23 37.3054 22.9804 37.2166 22.9424C37.1278 22.9044 37.0474 22.8487 36.9801 22.7786L27.1899 12.8523C27.1208 12.7841 27.0659 12.7026 27.0284 12.6125C26.9909 12.5225 26.9716 12.4257 26.9716 12.3279C26.9716 12.2302 26.9909 12.1334 27.0284 12.0433C27.0659 11.9533 27.1208 11.8717 27.1899 11.8036Z" stroke="#8A8A8A" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.5908 0.00488281C18.644 0.010556 18.6968 0.0211124 18.748 0.0361328L18.8496 0.0732422L27.4863 3.82031C27.7975 3.956 27.9999 4.25977 27.999 4.59668V4.88281L25.2773 6.32324L19.3633 8.8916V17.8164L24 15.8018V15.959L19.2197 18.0361V18.0381L24 15.9609V17.6523L18.8496 19.8877C18.6546 19.9719 18.4361 19.982 18.2353 19.9189L18.1504 19.8877L9.51367 16.1396L9.40332 16.082C9.15808 15.9302 9.00203 15.6645 9 15.3721V4.59668C8.99911 4.25968 9.20241 3.95595 9.51367 3.82031L18.1494 0.0732422L18.2852 0.0263672C18.3319 0.0145036 18.3802 0.00679393 18.4287 0.00292969L18.5908 0.00488281ZM10.583 14.9121L17.7803 18.0381V18.0361L10.583 14.9102V14.9121ZM10.7266 14.8154L17.6357 17.8164V8.8916L10.7266 5.8916V14.8154ZM18.5 7.57617L11.6348 4.59668L11.6328 4.59863L18.5 7.57812L25.3672 4.59863L25.3643 4.59668L18.5 7.57617ZM11.9932 4.59668L18.5 7.41895L25.0059 4.59668L18.5 1.76758L11.9932 4.59668ZM18.4394 0.146484C18.359 0.152931 18.28 0.173119 18.207 0.205078L9.57129 3.95215C9.39972 4.0269 9.26947 4.16303 9.20019 4.32617C9.2698 4.16409 9.4004 4.02953 9.57129 3.95508L18.207 0.207031C18.28 0.175072 18.359 0.154884 18.4394 0.148438C18.5602 0.139301 18.6815 0.159598 18.792 0.207031L27.4287 3.95508C27.5993 4.02948 27.7311 4.16342 27.8008 4.3252C27.7314 4.16231 27.6 4.02684 27.4287 3.95215L18.792 0.205078C18.6815 0.157614 18.5602 0.137346 18.4394 0.146484Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.6289 0C27.5839 0.000217454 29.9999 2.46547 30 5.52832V12.7998L27.8828 14.7041C28.0196 14.3204 28.0957 13.9056 28.0957 13.4717V5.52832C28.0956 3.56289 26.5694 1.9797 24.6289 1.97949H11.3711C9.4306 1.9797 7.90438 3.56289 7.9043 5.52832V13.4717C7.90437 15.4371 9.4306 17.0203 11.3711 17.0205H24.6289C24.8981 17.0205 25.1589 16.9884 25.4092 16.9307L23.1113 19H11.3711C8.41613 18.9998 6.00008 16.5345 6 13.4717V5.52832C6.00008 2.46547 8.41613 0.000217456 11.3711 0H24.6289ZM15.2559 4.49414C15.4185 4.49956 15.578 4.55256 15.7158 4.64648L21.793 8.61816C22.1122 8.82076 22.2295 9.19571 22.2295 9.5C22.2295 9.80434 22.1113 10.1792 21.792 10.3818L15.7168 14.3535C15.4268 14.5509 15.0492 14.5538 14.7568 14.3643L14.7539 14.3623C14.4655 14.1718 14.2938 13.8161 14.3027 13.46V5.52832C14.296 5.02308 14.6729 4.52218 15.1836 4.49512L15.2559 4.49414ZM16.1641 11.668L19.4922 9.49902L16.1641 7.32422V11.668Z" fill="#8A8A8A"/>
<path d="M32.6395 13.0655L34.6395 15.0655M38.5 4.66675V7.33341M35.9728 17.7322V20.3988M32.6395 6.66675V8.00008M39.8333 6.00008H37.1667M37.3062 19.0655H34.6395M33.3062 7.33341H31.9728M37.7329 10.8255L36.8795 9.97218C36.8045 9.89639 36.7152 9.83623 36.6168 9.79517C36.5184 9.75411 36.4128 9.73297 36.3062 9.73297C36.1996 9.73297 36.094 9.75411 35.9956 9.79517C35.8972 9.83623 35.8079 9.89639 35.7329 9.97218L24.8795 20.8255C24.8037 20.9005 24.7436 20.9898 24.7025 21.0882C24.6615 21.1866 24.6403 21.2922 24.6403 21.3988C24.6403 21.5055 24.6615 21.6111 24.7025 21.7095C24.7436 21.8079 24.8037 21.8972 24.8795 21.9722L25.7329 22.8255C25.8074 22.9021 25.8965 22.963 25.995 23.0046C26.0935 23.0462 26.1993 23.0676 26.3062 23.0676C26.4131 23.0676 26.5189 23.0462 26.6174 23.0046C26.7158 22.963 26.805 22.9021 26.8795 22.8255L37.7329 11.9722C37.8095 11.8976 37.8704 11.8085 37.9119 11.71C37.9535 11.6115 37.9749 11.5057 37.9749 11.3988C37.9749 11.292 37.9535 11.1862 37.9119 11.0877C37.8704 10.9892 37.8095 10.9001 37.7329 10.8255Z" stroke="#8A8A8A" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,25 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12686)">
<path d="M439.808 232.147C404.368 217.06 372.144 195.328 344.875 168.125C306.899 130.074 279.806 82.5466 266.411 30.4827C265.823 28.1703 264.481 26.1197 262.598 24.6551C260.714 23.1905 258.397 22.3953 256.011 22.3953C253.625 22.3953 251.307 23.1905 249.423 24.6551C247.54 26.1197 246.198 28.1703 245.611 30.4827C232.187 82.5399 205.09 130.062 167.125 168.125C139.853 195.325 107.63 217.056 72.192 232.147C58.3253 238.12 44.0747 242.92 29.4827 246.611C27.1561 247.182 25.0884 248.518 23.6102 250.403C22.132 252.288 21.3287 254.615 21.3287 257.011C21.3287 259.406 22.132 261.733 23.6102 263.618C25.0884 265.504 27.1561 266.839 29.4827 267.411C44.0747 271.08 58.2827 275.88 72.192 281.853C107.632 296.94 139.856 318.672 167.125 345.875C205.111 383.93 232.212 431.465 245.611 483.539C246.182 485.865 247.518 487.933 249.403 489.411C251.288 490.889 253.615 491.693 256.011 491.693C258.406 491.693 260.733 490.889 262.618 489.411C264.504 487.933 265.839 485.865 266.411 483.539C270.08 468.925 274.88 454.717 280.853 440.808C295.939 405.368 317.671 373.143 344.875 345.875C382.934 307.897 430.468 280.804 482.539 267.411C484.851 266.823 486.902 265.481 488.366 263.598C489.831 261.714 490.626 259.397 490.626 257.011C490.626 254.625 489.831 252.307 488.366 250.423C486.902 248.54 484.851 247.198 482.539 246.611C467.932 242.936 453.643 238.099 439.808 232.147Z" fill="#3186FF"/>
<path d="M439.808 232.147C404.368 217.06 372.144 195.328 344.875 168.125C306.899 130.074 279.806 82.5466 266.411 30.4827C265.823 28.1703 264.481 26.1197 262.598 24.6551C260.714 23.1905 258.397 22.3953 256.011 22.3953C253.625 22.3953 251.307 23.1905 249.423 24.6551C247.54 26.1197 246.198 28.1703 245.611 30.4827C232.187 82.5399 205.09 130.062 167.125 168.125C139.853 195.325 107.63 217.056 72.192 232.147C58.3253 238.12 44.0747 242.92 29.4827 246.611C27.1561 247.182 25.0884 248.518 23.6102 250.403C22.132 252.288 21.3287 254.615 21.3287 257.011C21.3287 259.406 22.132 261.733 23.6102 263.618C25.0884 265.504 27.1561 266.839 29.4827 267.411C44.0747 271.08 58.2827 275.88 72.192 281.853C107.632 296.94 139.856 318.672 167.125 345.875C205.111 383.93 232.212 431.465 245.611 483.539C246.182 485.865 247.518 487.933 249.403 489.411C251.288 490.889 253.615 491.693 256.011 491.693C258.406 491.693 260.733 490.889 262.618 489.411C264.504 487.933 265.839 485.865 266.411 483.539C270.08 468.925 274.88 454.717 280.853 440.808C295.939 405.368 317.671 373.143 344.875 345.875C382.934 307.897 430.468 280.804 482.539 267.411C484.851 266.823 486.902 265.481 488.366 263.598C489.831 261.714 490.626 259.397 490.626 257.011C490.626 254.625 489.831 252.307 488.366 250.423C486.902 248.54 484.851 247.198 482.539 246.611C467.932 242.936 453.643 238.099 439.808 232.147Z" fill="url(#paint0_linear_1471_12686)"/>
<path d="M439.808 232.147C404.368 217.06 372.144 195.328 344.875 168.125C306.899 130.074 279.806 82.5466 266.411 30.4827C265.823 28.1703 264.481 26.1197 262.598 24.6551C260.714 23.1905 258.397 22.3953 256.011 22.3953C253.625 22.3953 251.307 23.1905 249.423 24.6551C247.54 26.1197 246.198 28.1703 245.611 30.4827C232.187 82.5399 205.09 130.062 167.125 168.125C139.853 195.325 107.63 217.056 72.192 232.147C58.3253 238.12 44.0747 242.92 29.4827 246.611C27.1561 247.182 25.0884 248.518 23.6102 250.403C22.132 252.288 21.3287 254.615 21.3287 257.011C21.3287 259.406 22.132 261.733 23.6102 263.618C25.0884 265.504 27.1561 266.839 29.4827 267.411C44.0747 271.08 58.2827 275.88 72.192 281.853C107.632 296.94 139.856 318.672 167.125 345.875C205.111 383.93 232.212 431.465 245.611 483.539C246.182 485.865 247.518 487.933 249.403 489.411C251.288 490.889 253.615 491.693 256.011 491.693C258.406 491.693 260.733 490.889 262.618 489.411C264.504 487.933 265.839 485.865 266.411 483.539C270.08 468.925 274.88 454.717 280.853 440.808C295.939 405.368 317.671 373.143 344.875 345.875C382.934 307.897 430.468 280.804 482.539 267.411C484.851 266.823 486.902 265.481 488.366 263.598C489.831 261.714 490.626 259.397 490.626 257.011C490.626 254.625 489.831 252.307 488.366 250.423C486.902 248.54 484.851 247.198 482.539 246.611C467.932 242.936 453.643 238.099 439.808 232.147Z" fill="url(#paint1_linear_1471_12686)"/>
<path d="M439.808 232.147C404.368 217.06 372.144 195.328 344.875 168.125C306.899 130.074 279.806 82.5466 266.411 30.4827C265.823 28.1703 264.481 26.1197 262.598 24.6551C260.714 23.1905 258.397 22.3953 256.011 22.3953C253.625 22.3953 251.307 23.1905 249.423 24.6551C247.54 26.1197 246.198 28.1703 245.611 30.4827C232.187 82.5399 205.09 130.062 167.125 168.125C139.854 195.325 107.63 217.056 72.192 232.147C58.3253 238.12 44.0747 242.92 29.4827 246.611C27.1561 247.182 25.0884 248.518 23.6102 250.403C22.132 252.288 21.3287 254.615 21.3287 257.011C21.3287 259.406 22.132 261.733 23.6102 263.618C25.0884 265.504 27.1561 266.839 29.4827 267.411C44.0747 271.08 58.2827 275.88 72.192 281.853C107.632 296.94 139.856 318.672 167.125 345.875C205.111 383.93 232.212 431.465 245.611 483.539C246.182 485.865 247.518 487.933 249.403 489.411C251.288 490.889 253.615 491.693 256.011 491.693C258.406 491.693 260.733 490.889 262.618 489.411C264.504 487.933 265.839 485.865 266.411 483.539C270.08 468.925 274.88 454.717 280.853 440.808C295.939 405.368 317.671 373.143 344.875 345.875C382.934 307.897 430.468 280.804 482.539 267.411C484.851 266.823 486.902 265.481 488.366 263.598C489.831 261.714 490.626 259.397 490.626 257.011C490.626 254.625 489.831 252.307 488.366 250.423C486.902 248.54 484.851 247.198 482.539 246.611C467.932 242.936 453.643 238.099 439.808 232.147Z" fill="url(#paint2_linear_1471_12686)"/>
</g>
<defs>
<linearGradient id="paint0_linear_1471_12686" x1="149.333" y1="331.667" x2="234.667" y2="257" gradientUnits="userSpaceOnUse">
<stop stop-color="#08B962"/>
<stop offset="1" stop-color="#08B962" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_1471_12686" x1="170.667" y1="118.333" x2="245.333" y2="235.667" gradientUnits="userSpaceOnUse">
<stop stop-color="#F94543"/>
<stop offset="1" stop-color="#F94543" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_1471_12686" x1="74.6667" y1="289" x2="373.333" y2="257" gradientUnits="userSpaceOnUse">
<stop stop-color="#FABC12"/>
<stop offset="0.46" stop-color="#FABC12" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_1471_12686">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.8593 13C35.4827 13 36.0007 13.4988 36.0009 14.0996V22.9004C36.0007 23.5012 35.4827 24 34.8593 24H22.1425C21.5191 24 21.0011 23.5012 21.0009 22.9004V14.0996C21.0011 13.4988 21.5191 13 22.1425 13H34.8593ZM21.9794 21.2646V22.9004C21.9796 22.9953 22.0439 23.0566 22.1425 23.0566H34.8593C34.9579 23.0566 35.0222 22.9953 35.0224 22.9004V21.5547L32.2255 19.3984L29.9179 20.9307C29.746 21.0424 29.498 21.032 29.3369 20.9062L26.1982 18.4609L21.9794 21.2646ZM16.5009 10.5C16.777 10.5001 17.0009 10.7239 17.0009 11V17.5C17.001 18.3283 17.6727 18.9998 18.5009 19H18.7089L18.0615 18.3535C17.8665 18.1583 17.8665 17.8417 18.0615 17.6465C18.2567 17.4512 18.5742 17.4512 18.7695 17.6465L20.1835 19.0605C20.3785 19.2557 20.3784 19.5723 20.1835 19.7676L18.7695 21.1816C18.5742 21.3769 18.2567 21.3768 18.0615 21.1816C17.8666 20.9864 17.8664 20.6697 18.0615 20.4746L18.5361 20H18.5009C17.1204 19.9998 16.001 18.8806 16.0009 17.5V11C16.001 10.724 16.2249 10.5002 16.5009 10.5ZM22.1425 13.9424C22.0439 13.9424 21.9796 14.0047 21.9794 14.0996V20.1152L25.9384 17.4834C26.0045 17.4381 26.082 17.4096 26.162 17.4004C26.2907 17.3869 26.4244 17.4244 26.5244 17.5029L29.663 19.9531L31.9755 18.4219C32.1475 18.3102 32.3954 18.3204 32.5566 18.4463L35.0224 20.3467V14.0996C35.0222 14.0047 34.9579 13.9424 34.8593 13.9424H22.1425ZM29.6425 15.2002C30.4468 15.2003 31.1093 15.839 31.1093 16.6143C31.1093 17.3895 30.4469 18.0283 29.6425 18.0283C28.8381 18.0283 28.1747 17.3895 28.1747 16.6143C28.1748 15.839 28.8381 15.2002 29.6425 15.2002ZM29.6425 16.1426C29.3668 16.1426 29.1533 16.3485 29.1533 16.6143C29.1533 16.8801 29.3667 17.0859 29.6425 17.0859C29.9182 17.0859 30.1318 16.88 30.1318 16.6143C30.1318 16.3485 29.9182 16.1426 29.6425 16.1426ZM22.0917 0C23.6924 0.000102997 25.0009 1.29808 25.0009 2.91016V7.08984C25.0009 8.70192 23.6924 9.9999 22.0917 10H14.9111C13.3103 10 12.0009 8.70198 12.0009 7.08984V2.91016C12.0009 1.29802 13.3103 0 14.9111 0H22.0917ZM14.9111 1.04199C13.8598 1.04199 13.0331 1.87561 13.0331 2.91016V7.08984C13.0331 8.12439 13.8598 8.95801 14.9111 8.95801H22.0917C23.1429 8.95791 23.9697 8.12432 23.9697 7.08984V2.91016C23.9697 1.87568 23.1429 1.04209 22.0917 1.04199H14.9111ZM17.0146 2.36523C17.1026 2.36806 17.189 2.39596 17.2636 2.44531L20.5556 4.53613C20.7284 4.64278 20.7919 4.83988 20.7919 5C20.7919 5.16007 20.7283 5.35719 20.5556 5.46387L17.2646 7.55469C17.1075 7.65858 16.9024 7.66034 16.7441 7.56055L16.7431 7.55957C16.5867 7.45933 16.4941 7.27149 16.499 7.08398V2.91016C16.4953 2.64423 16.6989 2.38047 16.9755 2.36621L17.0146 2.36523ZM17.5068 6.1416L19.3095 5L17.5068 3.85449V6.1416ZM20.4999 5.22559L20.5234 5.19434C20.5303 5.1833 20.5364 5.17121 20.5419 5.15918C20.5308 5.1833 20.5167 5.20593 20.4999 5.22559Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,10 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12732)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M197.76 327.187L367.957 201.384C376.299 195.197 388.224 197.608 392.213 207.187C413.12 257.725 403.776 318.44 362.133 360.125C320.512 401.811 262.571 410.941 209.621 390.12L151.787 416.936C234.752 473.704 335.488 459.667 398.443 396.605C448.384 346.6 463.851 278.44 449.387 216.979L449.515 217.128C428.544 126.845 454.677 90.7493 508.181 16.9573C509.461 15.208 510.741 13.4586 512 11.6666L441.579 82.1733V81.96L197.696 327.229M162.624 357.757C103.061 300.797 113.344 212.669 164.139 161.832C201.707 124.221 263.275 108.861 317.013 131.432L374.72 104.765C362.726 95.9422 349.603 88.7672 335.701 83.432C300.753 69.1279 262.355 65.4777 225.337 72.9405C188.32 80.4032 154.335 98.6457 127.659 125.373C73.6213 179.475 56.6187 262.675 85.8027 333.672C107.605 386.728 71.872 424.253 35.8827 462.141C23.104 475.581 10.304 489 0 503.208L162.56 357.821" fill="#B6B6B6"/>
</g>
<defs>
<clipPath id="clip0_1471_12732">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,10 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1471_12743)">
<path d="M148.228 255.643L102.567 278.057L99.924 279.348L64.0174 296.98V325.672L113.435 301.418L117.364 299.492L139.326 288.706L145.968 285.454C148.837 273.378 152.193 262.214 155.914 251.876L148.228 255.643ZM432.49 117.736C431.012 114.312 429.43 110.958 427.761 107.637C427.552 107.225 427.343 106.777 427.1 106.364C408.825 70.8767 379.613 41.7882 343.81 23.4336C343.115 23.0725 318.563 11.8911 285.838 5.73274C276.222 3.92656 265.929 2.55047 255.305 2H122.372C90.1518 2 64.0174 27.7858 64.0174 59.5923V260.77L87.5957 249.209L88.6216 248.71L172.068 207.753L178.397 204.639C213.016 148.337 255.861 133.664 255.861 133.664V166.537C270.05 167.191 282.447 172.868 290.394 181.193C293.768 184.686 296.636 189.691 298.74 194.782C299.14 195.78 299.522 196.761 299.854 197.759C300.532 199.633 301.071 201.457 301.487 203.195C302.079 205.603 303.07 207.581 304.287 209.335C304.287 209.335 304.287 209.388 304.323 209.405C305.521 211.107 306.93 212.553 308.408 213.929C313.555 218.745 319.433 222.564 319.433 231.853C319.433 236.05 317.607 239.68 314.476 242.862C312.72 244.668 310.565 246.303 308.095 247.851C307.556 248.177 306.983 248.504 306.408 248.814L306.339 248.865C305.244 249.468 304.078 250.069 302.896 250.638C302.653 250.758 302.392 250.861 302.131 250.982C302.079 250.999 302.009 251.016 301.957 251.05C294.567 254.387 278.866 259.48 275.927 260.391C275.231 260.58 274.432 260.804 273.579 261.044C270.919 261.802 267.789 262.696 265.929 263.246C265.72 263.281 265.529 263.35 265.338 263.418C264.729 263.608 264.364 263.728 264.33 263.745C262.885 264.176 261.478 264.691 260.138 265.311C259.791 265.466 259.443 265.638 259.096 265.81C255.409 267.581 252.071 269.937 249.184 272.794C242.455 279.416 238.317 288.534 238.317 298.63C238.317 303.655 239.378 308.47 241.22 312.858C242.194 315.025 243.342 317.106 244.629 319.101C246.262 321.785 248.158 324.485 250.245 327.221C250.488 327.582 250.767 327.926 251.027 328.27C258.139 337.473 267.32 346.969 276.622 356.757C279.804 360.094 283.005 363.448 286.117 366.854C288.881 369.864 291.628 372.91 294.271 375.988C295.054 376.882 295.837 377.795 296.585 378.706C301.523 377.657 313.033 374.388 314.094 374.061C340.419 365.737 364.311 351.94 384.36 334.033C384.655 333.792 384.933 333.552 385.229 333.293C421.258 300.73 444.75 254.852 447.687 203.573C447.862 200.614 447.949 197.673 447.983 194.68C448 194.009 448 193.32 448 192.632C447.949 166.038 442.453 140.717 432.49 117.736ZM135.605 326.739L135.448 326.808L64.0174 361.848V390.541L135.326 355.552C135.552 352.25 135.831 348.947 136.161 345.576C136.196 344.99 136.265 344.389 136.317 343.803L136.387 343.081C136.822 338.54 137.361 333.93 137.987 329.234C138.161 327.943 138.334 326.618 138.526 325.311L135.605 326.739ZM156.522 505.434C155.775 503.696 154.958 501.717 154.088 499.464C147.394 482.176 137.517 449.923 134.996 405.214C134.753 400.983 134.579 396.665 134.474 392.227L68.6426 424.481L64 426.768V456.391C64 463.942 65.4606 471.132 68.1558 477.739C76.7282 498.983 97.7679 514 122.355 514H160.452V513.828C160.104 513.157 158.627 510.336 156.522 505.434Z" fill="#B6B6B6"/>
</g>
<defs>
<clipPath id="clip0_1471_12743">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 9C12.7761 9 13 9.22386 13 9.5V20C13 20.2761 13.2239 20.5 13.5 20.5H28C28.2761 20.5 28.5 20.7239 28.5 21C28.5 21.2761 28.2761 21.5 28 21.5H13.5C12.6716 21.5 12 20.8284 12 20V9.5C12 9.22386 12.2239 9 12.5 9ZM14.5 7C14.7761 7 15 7.22386 15 7.5V18C15 18.2761 15.2239 18.5 15.5 18.5H30C30.2761 18.5 30.5 18.7239 30.5 19C30.5 19.2761 30.2761 19.5 30 19.5H15.5C14.6716 19.5 14 18.8284 14 18V7.5C14 7.22386 14.2239 7 14.5 7ZM16.5 5C16.7761 5 17 5.22386 17 5.5V16C17 16.2761 17.2239 16.5 17.5 16.5H32C32.2761 16.5 32.5 16.7239 32.5 17C32.5 17.2761 32.2761 17.5 32 17.5H17.5C16.6716 17.5 16 16.8284 16 16V5.5C16 5.22386 16.2239 5 16.5 5ZM33.7061 2.5C34.4126 2.5 34.9999 3.08968 35 3.7998V14.2002C34.9999 14.9103 34.4126 15.5 33.7061 15.5H19.2939C18.5874 15.5 18.0001 14.9103 18 14.2002V3.7998C18.0001 3.08968 18.5874 2.5 19.2939 2.5H33.7061ZM19.1084 12.2676V14.2002C19.1085 14.3124 19.1814 14.3856 19.293 14.3857H33.7061C33.8179 14.3857 33.8915 14.3125 33.8916 14.2002V12.6094L30.7207 10.0615L28.1055 11.873C27.9107 12.005 27.6299 11.9923 27.4473 11.8438L23.8896 8.95312L19.1084 12.2676ZM19.2939 3.61426C19.1821 3.61426 19.1085 3.68744 19.1084 3.7998V10.9092L23.5957 7.79883C23.6707 7.74519 23.7587 7.71107 23.8496 7.7002C23.9954 7.68428 24.1465 7.72944 24.2598 7.82227L27.8164 10.7178L30.4385 8.90723C30.6334 8.7753 30.9141 8.78784 31.0967 8.93652L33.8916 11.1826V3.7998C33.8915 3.68747 33.8179 3.61426 33.7061 3.61426H19.2939ZM27.7939 5.09961C28.7054 5.09987 29.4561 5.8554 29.4561 6.77148C29.456 7.68754 28.7054 8.44213 27.7939 8.44238C26.8823 8.44238 26.1309 7.6877 26.1309 6.77148C26.1309 5.85524 26.8823 5.09961 27.7939 5.09961ZM27.7939 6.21387C27.4814 6.21387 27.2393 6.45737 27.2393 6.77148C27.2393 7.08557 27.4814 7.32812 27.7939 7.32812C28.1062 7.32788 28.3476 7.08542 28.3477 6.77148C28.3477 6.45752 28.1063 6.21411 27.7939 6.21387Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,11 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1683_13217)">
<path d="M33.2579 14.2434C33.2579 8.04845 24.8306 0.0257698 24.4718 -0.312477C24.2037 -0.565892 23.7862 -0.561497 23.5226 -0.305153C23.1637 0.0434776 14.7439 8.30814 14.7439 14.2436C14.7439 19.3484 18.8966 23.5 24.0003 23.5C29.1067 23.5015 33.2579 19.3486 33.2579 14.2434ZM24.0015 22.1299C19.6538 22.1299 16.1165 18.5924 16.1165 14.2449C16.1165 9.63656 22.2409 2.98187 24.009 1.15676C25.7829 2.94092 31.8885 9.42993 31.8885 14.2449C31.887 18.5926 28.349 22.1299 24.0015 22.1299Z" fill="#8A8A8A"/>
<path d="M28.4502 12.5882C28.0766 12.6482 27.8218 12.9998 27.8804 13.3733C28.1265 14.9143 27.5918 16.2605 26.0933 17.8748C25.8355 18.1516 25.8516 18.5852 26.1284 18.843C26.2603 18.9661 26.4273 19.0261 26.5943 19.0261C26.7788 19.0261 26.9619 18.9529 27.0967 18.8064C28.3037 17.5071 29.6381 15.6907 29.2339 13.1552C29.1753 12.7831 28.8222 12.5295 28.4502 12.5882Z" fill="#8A8A8A"/>
</g>
<defs>
<clipPath id="clip0_1683_13217">
<rect width="48" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 1.2002C18.4418 1.2002 18.7998 1.55817 18.7998 2V5.2002H29C29.9941 5.2002 30.7998 6.00589 30.7998 7V17.7002H34C34.4418 17.7002 34.7998 18.0582 34.7998 18.5C34.7998 18.9418 34.4418 19.2998 34 19.2998H30.7998V22.5C30.7998 22.9418 30.4418 23.2998 30 23.2998C29.5582 23.2998 29.2002 22.9418 29.2002 22.5V19.2998H19C18.0059 19.2998 17.2002 18.4941 17.2002 17.5V6.7998H14C13.5582 6.7998 13.2002 6.44183 13.2002 6C13.2002 5.55817 13.5582 5.2002 14 5.2002H17.2002V2C17.2002 1.55817 17.5582 1.2002 18 1.2002ZM18.7998 17.5C18.7998 17.6105 18.8895 17.7002 19 17.7002H29.2002V7C29.2002 6.88954 29.1105 6.79981 29 6.7998H18.7998V17.5Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.0035 1C17.9391 1 13 5.93909 13 12.0035C13 18.068 17.9391 23 24.0035 23C30.068 23 35 18.068 35 12.0035C35 5.93909 30.068 1 24.0035 1ZM24.0035 2.83353C29.0778 2.83353 33.1665 6.92919 33.1665 12.0035C33.1665 17.0779 29.0776 21.1665 24.0035 21.1665C18.9292 21.1665 14.8335 17.0776 14.8335 12.0035C14.8335 6.92949 18.9292 2.83353 24.0035 2.83353Z" fill="#8A8A8A"/>
<path d="M24.0012 1.91791C21.3277 1.91791 18.762 2.98021 16.871 4.87092C14.9801 6.76174 13.9168 9.32742 13.9168 12.0023C13.9168 14.6772 14.9802 17.2415 16.871 19.1325C18.7618 21.0234 21.3275 22.0867 24.0012 22.0867V1.91791Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 718 B

View File

@@ -0,0 +1,4 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 1.2002C14.4418 1.2002 14.7998 1.55817 14.7998 2V8.00684C14.8958 7.83091 14.9994 7.65581 15.1113 7.48438C15.6603 6.64373 16.4355 5.78389 17.3633 5.01074C19.2091 3.47256 21.7588 2.2002 24.5 2.2002C29.8381 2.20019 35.7998 5.96117 35.7998 13.5C35.7998 13.9418 35.4418 14.2998 35 14.2998C34.5582 14.2998 34.2002 13.9418 34.2002 13.5C34.2002 7.03883 29.1619 3.7998 24.5 3.7998C22.2413 3.79981 20.0409 4.86081 18.3867 6.23926C17.5645 6.92442 16.9022 7.6688 16.4512 8.35938C16.1004 8.89656 15.9082 9.35442 15.835 9.7002H20.5C20.9418 9.7002 21.2998 10.0582 21.2998 10.5C21.2998 10.9418 20.9418 11.2998 20.5 11.2998H15C14.0059 11.2998 13.2002 10.4941 13.2002 9.5V2C13.2002 1.55817 13.5582 1.2002 14 1.2002Z" fill="#8A8A8A"/>
<path d="M18.3446 15.6464C17.9126 15.8958 17.758 16.4565 18.0038 16.8823L21.6038 23.1177C21.8497 23.5435 22.4126 23.69 22.8446 23.4406L31.6554 18.3537C32.0874 18.1043 32.242 17.5435 31.9962 17.1177L28.3962 10.8823C28.1503 10.4565 27.5874 10.31 27.1554 10.5594L18.3446 15.6464ZM18.7303 16.3145L27.5411 11.2275C27.6095 11.188 27.6795 11.2062 27.7184 11.2736L30.2738 15.6996L27.7878 15.3394C27.6246 15.3147 27.4487 15.4064 27.3753 15.5544L26.3994 17.5653L23.2225 17.0844C23.121 17.0687 23.0133 17.095 22.9296 17.156C22.8778 17.1947 22.8358 17.2462 22.8085 17.3049L21.1425 20.7535L18.6816 16.491C18.6426 16.4236 18.6619 16.3539 18.7303 16.3145ZM24.4407 14.2052C23.8834 14.527 23.6855 15.245 24.0027 15.7943C24.3198 16.3436 25.0406 16.5312 25.5979 16.2095C26.1552 15.8877 26.3531 15.1697 26.0359 14.6204C25.7188 14.0711 24.998 13.8835 24.4407 14.2052ZM24.8264 14.8733C25.0175 14.763 25.2494 14.8234 25.3582 15.0117C25.4669 15.2 25.4032 15.4311 25.2121 15.5414C25.0211 15.6517 24.7891 15.5913 24.6804 15.403C24.5717 15.2147 24.6354 14.9836 24.8264 14.8733ZM23.3884 17.8934L26.5633 18.3707C26.7264 18.3954 26.9023 18.3037 26.9757 18.1558L27.948 16.1469L30.7679 16.5556L31.3183 17.509C31.3572 17.5764 31.338 17.6461 31.2696 17.6856L22.4588 22.7725C22.3904 22.812 22.3205 22.7938 22.2815 22.7264L21.6126 21.5677L23.3884 17.8934Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.4316 13.4326C21.7455 13.1187 22.2535 13.1188 22.5674 13.4326C22.8813 13.7465 22.8813 14.2544 22.5674 14.5684L15.6777 21.4502H18.25C18.692 21.4502 19.0498 21.808 19.0498 22.25C19.0498 22.692 18.692 23.0498 18.25 23.0498H13.75C13.5592 23.0498 13.3754 22.9822 13.2305 22.8584L13.2295 22.8574C13.1888 22.8217 13.1504 22.7816 13.1172 22.7373V22.7363C13.0211 22.6128 12.9622 22.4629 12.9502 22.3066V17.75C12.9502 17.308 13.308 16.9502 13.75 16.9502C14.192 16.9502 14.5498 17.308 14.5498 17.75V20.3213L21.4316 13.4326ZM34.75 7.9502C34.9619 7.9502 35.1664 8.03452 35.3164 8.18457C35.4662 8.33455 35.5498 8.53829 35.5498 8.75V22.25C35.5498 22.4619 35.4665 22.6664 35.3164 22.8164C35.1664 22.9665 34.9619 23.0498 34.75 23.0498H21.25C20.8081 23.0497 20.4502 22.692 20.4502 22.25C20.4503 21.8081 20.8081 21.4503 21.25 21.4502H33.9502V8.75C33.9503 8.30811 34.3081 7.95026 34.75 7.9502ZM27.25 0.450195C27.6919 0.450195 28.0497 0.808072 28.0498 1.25C28.0498 1.692 27.692 2.0498 27.25 2.0498H14.5498V14.75C14.5498 14.9619 14.4665 15.1664 14.3164 15.3164C14.1664 15.4665 13.9619 15.5498 13.75 15.5498C13.5383 15.5498 13.3346 15.4662 13.1846 15.3164C13.0345 15.1664 12.9502 14.9619 12.9502 14.75V1.25C12.9503 0.808109 13.3081 0.450255 13.75 0.450195H27.25ZM35.5498 5.75C35.5498 6.192 35.192 6.5498 34.75 6.5498C34.3081 6.54974 33.9502 6.19196 33.9502 5.75V3.17773L27.0684 10.0674C26.9184 10.2183 26.7131 10.3047 26.5 10.3047C26.2872 10.3046 26.0825 10.2189 25.9326 10.0684C25.7817 9.91843 25.6953 9.71311 25.6953 9.5C25.6953 9.28687 25.7817 9.08159 25.9326 8.93164L32.8223 2.0498H30.25C29.808 2.0498 29.4502 1.692 29.4502 1.25C29.4502 0.808002 29.808 0.450195 30.25 0.450195H34.75C34.9408 0.450201 35.1246 0.517855 35.2695 0.641602L35.2705 0.642578C35.3109 0.678079 35.3488 0.717743 35.3818 0.761719L35.4473 0.859375C35.5053 0.961394 35.5408 1.07563 35.5498 1.19336V5.75Z" fill="#8A8A8A" stroke="#8A8A8A" stroke-width="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.5386 22.7695C25.8783 22.7697 26.1538 23.045 26.1538 23.3848C26.1537 23.7245 25.8783 23.9998 25.5386 24H15.3843C15.0445 23.9999 14.7691 23.7245 14.769 23.3848C14.769 23.0449 15.0445 22.7696 15.3843 22.7695H25.5386ZM32.6147 19.3848C32.9546 19.3848 33.231 19.6601 33.231 20C33.231 20.3399 32.9546 20.6152 32.6147 20.6152H15.3843C15.0445 20.6152 14.769 20.3398 14.769 20C14.769 19.6602 15.0445 19.3848 15.3843 19.3848H32.6147ZM32.6147 16C32.9546 16 33.2309 16.2754 33.231 16.6152C33.231 16.9551 32.9546 17.2305 32.6147 17.2305H15.3843C15.0445 17.2304 14.769 16.9551 14.769 16.6152C14.7691 16.2755 15.0445 16.0001 15.3843 16H32.6147ZM31.8257 0C32.593 0 33.2308 0.614038 33.231 1.35352V12.1846C33.231 12.9242 32.5931 13.5381 31.8257 13.5381H16.1733C15.4061 13.5379 14.769 12.9241 14.769 12.1846V1.35352C14.7692 0.614161 15.4062 0.000205504 16.1733 0H31.8257ZM15.9731 10.1719V12.1846C15.9731 12.3016 16.052 12.3778 16.1733 12.3779H31.8257C31.9472 12.3779 32.0269 12.3017 32.0269 12.1846V10.5283L28.5835 7.875L25.7427 9.76074C25.531 9.89807 25.2261 9.88528 25.0278 9.73047L21.1655 6.7207L15.9731 10.1719ZM16.1733 1.16016C16.0522 1.16032 15.9733 1.2367 15.9731 1.35352V8.75781L20.8452 5.51758C20.9267 5.4617 21.0228 5.42637 21.1216 5.41504C21.2799 5.39849 21.4439 5.44534 21.5669 5.54199L25.4292 8.55859L28.2769 6.67285C28.4885 6.53537 28.7934 6.54826 28.9917 6.70312L32.0269 9.04199V1.35352C32.0267 1.2366 31.9471 1.16016 31.8257 1.16016H16.1733ZM25.4048 2.70801C26.3946 2.70816 27.2104 3.49419 27.2104 4.44824C27.2104 5.40234 26.3947 6.1893 25.4048 6.18945C24.4148 6.18945 23.5981 5.40243 23.5981 4.44824C23.5982 3.4941 24.4148 2.70801 25.4048 2.70801ZM25.4048 3.86816C25.0654 3.86816 24.8023 4.12117 24.8022 4.44824C24.8022 4.77537 25.0654 5.02832 25.4048 5.02832C25.7441 5.02818 26.0063 4.77528 26.0063 4.44824C26.0063 4.12126 25.744 3.86831 25.4048 3.86816Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.0002 10.9506C22.4541 9.6908 20.4514 9.1392 18.4787 9.42816C18.2607 8.80882 18.1488 8.15727 18.1496 7.50043V7.49945C18.1498 5.41028 19.2656 3.47869 21.0754 2.43304C22.885 1.38855 25.1153 1.38845 26.925 2.43304L27.2561 2.6391C28.8699 3.72034 29.8497 5.5406 29.8498 7.49945V7.50043C29.8506 8.15737 29.7388 8.80876 29.5207 9.42816C27.5483 9.13945 25.5462 9.69102 24.0002 10.9506ZM22.2297 13.0707C20.9335 12.6588 19.8225 11.8085 19.0862 10.6674C19.2239 10.6578 19.3618 10.6504 19.5002 10.6498C20.7921 10.6481 22.0453 11.0775 23.0656 11.8657C22.7457 12.2373 22.466 12.6415 22.2297 13.0707ZM25.7697 13.0698C25.5339 12.6414 25.254 12.2386 24.9348 11.8676C25.954 11.079 27.2077 10.6501 28.4992 10.6508C28.6376 10.6514 28.7756 10.6578 28.9133 10.6674C28.1769 11.8085 27.066 12.6579 25.7697 13.0698ZM19.0969 22.3315C17.6196 22.2291 16.2357 21.5709 15.2248 20.4887C14.2139 19.4065 13.6505 17.9801 13.6496 16.4995C13.6507 15.2383 14.0599 14.0112 14.8156 13.0014C15.5468 12.0225 16.566 11.2995 17.7287 10.9301C18.5931 12.5003 20.0112 13.6917 21.7072 14.2719C21.3242 15.4366 21.2474 16.6814 21.4856 17.8852C21.7241 19.0859 22.2682 20.2045 23.0627 21.1332C21.9324 22.0019 20.5223 22.4303 19.0969 22.3315ZM29.6819 22.2426C28.0119 22.5816 26.28 22.1738 24.9367 21.1332C25.7315 20.2044 26.2763 19.0853 26.5149 17.8842C26.753 16.6805 26.6752 15.4365 26.2922 14.2719C27.988 13.692 29.405 12.5008 30.2697 10.9311C31.9037 11.4445 33.2334 12.6462 33.9035 14.226C34.5881 15.843 34.5115 17.682 33.6926 19.2348C32.8746 20.7878 31.402 21.8925 29.6819 22.2426ZM24.4133 13.3334C24.1376 13.3528 23.8609 13.3529 23.5852 13.3334C23.7115 13.1369 23.8508 12.9495 24.0002 12.77C24.1494 12.9492 24.2871 13.1372 24.4133 13.3334ZM24.0002 20.228C23.3622 19.4615 22.9284 18.5463 22.7453 17.5639C22.559 16.5634 22.6407 15.5335 22.9748 14.5746C23.6529 14.6745 24.3414 14.6744 25.0188 14.5746C25.3546 15.5336 25.4377 16.5632 25.2522 17.5629C25.07 18.5453 24.6372 19.4615 24.0002 20.228ZM35.6506 16.3696H35.6467C35.6172 14.8949 35.1354 13.4636 34.2619 12.2729C33.3925 11.087 32.1799 10.2008 30.7903 9.72797C31.027 9.00974 31.1505 8.25806 31.1496 7.50043C31.1506 5.39267 30.2222 3.3924 28.6115 2.03265C27.0017 0.673924 24.873 0.0954168 22.7961 0.449646C20.7192 0.803888 18.9039 2.05694 17.8362 3.8725C16.793 5.64585 16.5674 7.77997 17.2082 9.72894C15.2317 10.3959 13.6402 11.8926 12.8586 13.8334C12.0592 15.8203 12.1936 18.0622 13.2238 19.9409C14.2542 21.8195 16.0735 23.1361 18.1789 23.5297C20.2439 23.9157 22.3716 23.3717 23.9992 22.0502C25.3822 23.1703 27.134 23.737 28.9133 23.6332C30.7347 23.5279 32.446 22.73 33.6985 21.4047C34.9499 20.0786 35.6486 18.3245 35.6506 16.5004V16.3696Z" fill="#8A8A8A" stroke="#8A8A8A" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.1625 8.91744L23.6625 14.1674C23.8744 14.2743 24.1256 14.2743 24.3375 14.1674L34.8375 8.91744C35.0878 8.789 35.2453 8.53119 35.2453 8.24996C35.2453 7.96872 35.0878 7.71089 34.8375 7.58247L24.3375 2.33247C24.1256 2.22559 23.8744 2.22559 23.6625 2.33247L13.1625 7.58247C12.9122 7.71091 12.7547 7.96872 12.7547 8.24996C12.7547 8.53119 12.9122 8.78902 13.1625 8.91744ZM23.9999 3.84L32.82 8.25L23.9999 12.66L15.1799 8.25L23.9999 3.84ZM35.1674 11.6626C35.353 12.031 35.2049 12.4801 34.8374 12.6676L24.3374 17.9176C24.1255 18.0244 23.8743 18.0244 23.6624 17.9176L13.1624 12.6676C12.8258 12.4651 12.7021 12.0366 12.8802 11.6869C13.0574 11.3363 13.4746 11.1816 13.8374 11.3326L23.9999 16.41L34.1625 11.3326C34.531 11.1469 34.9799 11.2951 35.1674 11.6626ZM35.1674 15.4126C35.353 15.781 35.2049 16.2301 34.8374 16.4176L24.3374 21.6676C24.1255 21.7744 23.8743 21.7744 23.6624 21.6676L13.1624 16.4176C12.8258 16.2151 12.7021 15.7866 12.8802 15.4369C13.0574 15.0863 13.4746 14.9316 13.8374 15.0826L23.9999 20.16L34.1625 15.0826C34.531 14.8969 34.9799 15.0451 35.1674 15.4126Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,15 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1683_13310)">
<rect x="12.9001" y="0.400061" width="22.7" height="22.7" rx="7.35" fill="#1C1C24" stroke="#8A8A8A" stroke-width="1.3"/>
<circle cx="24.35" cy="11.35" r="7.35" fill="url(#paint0_radial_1683_13310)"/>
</g>
<defs>
<radialGradient id="paint0_radial_1683_13310" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.25 11.65) rotate(-139.289) scale(8.50911)">
<stop offset="0.00961538" stop-color="white"/>
<stop offset="1" stop-color="#686868"/>
</radialGradient>
<clipPath id="clip0_1683_13310">
<rect width="48" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 729 B

View File

@@ -0,0 +1,3 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.5488 2.5C32.0541 2.50024 33.2763 3.71416 33.2763 5.20898V13.1113L35.0712 11.3291C35.2839 11.1179 35.628 11.1179 35.8407 11.3291C36.0531 11.5403 36.0533 11.8816 35.8407 12.0928H35.8447L33.1171 14.8008C33.0681 14.8494 33.0069 14.8936 32.9365 14.9199C32.8713 14.9472 32.8007 14.9638 32.7294 14.9639C32.659 14.9638 32.5868 14.9473 32.5214 14.9199C32.456 14.8925 32.3959 14.8545 32.3417 14.8008L29.9999 12.4766V15.5996C29.9999 16.0913 29.5856 16.5 29.0868 16.5H18.913C18.4142 16.5 17.9999 16.0913 17.9999 15.5996V13.3271C17.8586 13.3266 17.723 13.279 17.6142 13.1709L15.8193 11.3887V19.291C15.8194 20.1845 16.5562 20.9158 17.456 20.916H30.5478C31.4476 20.9159 32.1834 20.1845 32.1835 19.291V18.208C32.1835 17.9111 32.4287 17.6671 32.7275 17.666C33.0265 17.6667 33.2714 17.9109 33.2714 18.208V19.291C33.2713 20.7857 32.0492 21.9998 30.5439 22H17.4511C15.946 21.9996 14.7237 20.7856 14.7236 19.291V11.3887L12.9286 13.1709C12.7159 13.3818 12.3717 13.382 12.1591 13.1709C11.9469 12.9598 11.9469 12.6184 12.1591 12.4072L14.8876 9.69922C14.9417 9.64553 15.0021 9.60748 15.0673 9.58008C15.1981 9.5263 15.3505 9.52641 15.4814 9.58008C15.5519 9.60647 15.613 9.6505 15.662 9.69922L17.9999 12.0195V8.40039C17.9999 7.90871 18.4142 7.5 18.913 7.5H29.0868C29.5856 7.5 29.9999 7.90871 29.9999 8.40039V11.1709C30.1386 11.1713 30.2768 11.2239 30.3827 11.3291L32.1777 13.1113V5.20898C32.1777 4.31525 31.441 3.58398 30.5409 3.58398H17.455C16.5553 3.58438 15.8193 4.31549 15.8193 5.20898V6.29199C15.8191 6.58944 15.573 6.83398 15.2734 6.83398C14.9741 6.83359 14.7286 6.5892 14.7284 6.29199V5.20898C14.7284 3.71427 15.9508 2.50043 17.456 2.5H30.5488ZM18.7822 14.2617V15.5996C18.7822 15.6775 18.834 15.7285 18.913 15.7285H29.0868C29.1658 15.7285 29.2177 15.6775 29.2177 15.5996V14.499L26.9794 12.7354L25.1327 13.9893C24.9953 14.0803 24.7976 14.0714 24.6689 13.9688L22.1572 11.9678L18.7822 14.2617ZM18.913 8.27148C18.834 8.27148 18.7822 8.32254 18.7822 8.40039V13.3223L21.9491 11.168C22.0021 11.1308 22.0647 11.1072 22.1288 11.0996C22.2317 11.0886 22.3379 11.1204 22.4179 11.1846L24.9296 13.1895L26.7802 11.9355C26.9177 11.8444 27.1153 11.8533 27.2441 11.9561L29.2177 13.5107V8.40039C29.2177 8.32254 29.1658 8.27148 29.0868 8.27148H18.913ZM24.913 9.2998C25.5565 9.29981 26.0868 9.82277 26.0868 10.457C26.0868 11.0913 25.5565 11.6143 24.913 11.6143C24.2695 11.6143 23.7392 11.0914 23.7392 10.457C23.7392 9.82276 24.2695 9.2998 24.913 9.2998ZM24.913 10.0713C24.6924 10.0713 24.5215 10.2396 24.5214 10.457C24.5214 10.6745 24.6924 10.8428 24.913 10.8428C25.1336 10.8428 25.3046 10.6745 25.3046 10.457C25.3046 10.2396 25.1336 10.0713 24.913 10.0713Z" fill="#8A8A8A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,21 @@
<svg width="48" height="24" viewBox="0 0 48 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="12" width="24" height="24" rx="8" fill="url(#paint0_linear_1814_3711)"/>
<circle cx="24.35" cy="12.35" r="7.35" fill="url(#paint1_radial_1814_3711)"/>
<circle cx="24.35" cy="12.35" r="7.35" fill="url(#paint2_radial_1814_3711)"/>
<defs>
<linearGradient id="paint0_linear_1814_3711" x1="24" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
<stop stop-color="#D18EFF"/>
<stop offset="1" stop-color="#5C91FD"/>
</linearGradient>
<radialGradient id="paint1_radial_1814_3711" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.35 12.35) rotate(90) scale(7.35)">
<stop stop-color="#2BECFF"/>
<stop offset="0.37" stop-color="#F03CFF"/>
<stop offset="0.72" stop-color="#133AFF"/>
<stop offset="1" stop-color="#34F0FF"/>
</radialGradient>
<radialGradient id="paint2_radial_1814_3711" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.35 12.35) rotate(90) scale(7.35)">
<stop stop-color="#8998FF"/>
<stop offset="1" stop-color="#8998FF" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More