Prior to the release of subgraphs, there was a single graph accessed
through `app.graph`. Now that there's multiple graphs, there's a lot of
code that needs to be reviewed and potentially updated depending on if
it cares about nearby nodes, all nodes, or something else requiring
specific attention.
This was done by simply changing the type of `app.graph` to unknown so
the typechecker will complain about every place it's currently used.
References were then updated to `app.rootGraph` if the previous usage
was correct, or actually rewritten.
By not getting rid of `app.graph`, this change already ensures that
there's no loss of functionality for custom nodes, but the prior typing
of `app.graph` can always be restored if future dissuasion of
`app.graph` usage creates issues.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7399-Cleanup-app-graph-usage-2c76d73d365081178743dfdcf07f44d0)
by [Unito](https://www.unito.io)
## Summary
Reduce lower level font definitions in most places. Default to Inter.
See #6912
## Review Focus
Comic Sans is still an option...
## 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-7220-Style-Font-Consistency-2c26d73d365081348f2dd8909dd9bb8f)
by [Unito](https://www.unito.io)
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Increases the `/features` endpoint polling interval (runtime config
polling) from the current 30 seconds to 10 minutes.
## Background
The polling was originally added for two main purposes:
1. Server alert badges
2. Extra assurance that states are synchronized between frontend and
backend
However, both of these use cases are not critical:
- Server alert badges are unlikely to be needed in practice. WebSocket
(WS) can be used eventually as an alternative
- For synchronization, the system should be redesigned to be explicit
about marking client state as stale, or use WebSocket/Server-Sent Events
(WS/SSE) on a per-data basis rather than polling for the entire feature
flag set
## Motivation
The reason to reduce polling frequency is that per-user feature flags
are being added, which will make `/features` endpoint handling
significantly heavier. The endpoint will no longer just return static
JSON with high cache age, making frequent polling more costly.
## Future Considerations
- Eventually migrate server alert badges to use WebSocket
- Design a system that explicitly marks client state as stale when
needed
- Consider using WebSocket or Server-Sent Events for targeted data
updates instead of polling entire feature flag set
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7100-cloud-increase-feature-flag-polling-interval-to-10min-from-30s-2bd6d73d365081dfa8abeec901d0d975)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
This PR refactors the Load3d 3D rendering system to remove its direct
dependency on LGraphNode, making it a more decoupled and reusable
component. The core rendering engine is now framework-agnostic and can
be used in any context, not just within LiteGraph nodes.
## Changes
1. Decoupled Load3d from LGraphNode
- Before: Load3d directly accessed node.widgets and node.properties
- After: Load3d accepts optional parameters and callbacks, delegating
node integration to the calling code
2. Event-Driven State Management
- Removed internal storage from Load3d core components
- Camera, controls, and view helper managers now emit cameraChanged
events instead of directly storing state
- External code (e.g., useLoad3d) listens to events and handles
persistence to node.properties
3. Reactive Dimension Updates
- Introduced getDimensions callback to support reactive dimension
updates
- Fixes the issue where dimension changes in vueNodes mode required a
refresh
- The callback is invoked on every render to get fresh width/height
values
4. Improved Configuration System
- Load3DConfiguration now accepts properties: Dictionary<NodeProperty |
undefined> instead of custom storage
interface
- Uses official LiteGraph type definitions (Dictionary, NodeProperty)
- More semantic parameter naming: storage → properties
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6707-refactor-remove-node-as-dependency-in-3d-node-2ab6d73d365081ffac1cdce354781ce8)
by [Unito](https://www.unito.io)
Update the desktop guide links to make them platform and locale-aware
Edited by Terry:
Refactor external link management by introducing a centralized
useExternalLink composable with automatic locale and platform detection
for documentation URLs.
- Created useExternalLink composable - A new centralized utility for
managing all external links
- Dynamic docs URL builder (buildDocsUrl) - Automatically constructs
docs.comfy.org URLs with:
- Locale detection (Chinese vs English)
- Platform detection (macOS vs Windows for desktop)
- Flexible path construction with options
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-4471-Add-platform-and-locale-aware-desktop-guide-URL-2346d73d3650815ea4a4dd64be575bbe)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Terry Jia <terryjia88@gmail.com>
## Summary
This PR refactors the mask editor from a vanilla JavaScript
implementation to Vue 3 + Composition API, aligning it with the ComfyUI
frontend's modern architecture. This is a structural refactor without UI
changes - all visual appearances and user interactions remain identical.
Net change: +1,700 lines (mostly tests)
## Changes
- Converted from class-based managers to Vue 3 Composition API
- Migrated state management to Pinia stores (maskEditorStore,
maskEditorDataStore)
- Split monolithic managers into focused composables:
- useBrushDrawing - Brush rendering and drawing logic
- useCanvasManager - Canvas lifecycle and operations
- useCanvasTools - Tool-specific canvas operations
- usePanAndZoom - Pan and zoom functionality
- useToolManager - Tool selection and coordination
- useKeyboard - Keyboard shortcuts
- useMaskEditorLoader/Saver - Data loading and saving
- useCoordinateTransform - Coordinate system transformations
- Replaced imperative DOM manipulation with Vue components
- Added comprehensive test coverage
## What This PR Does NOT Change
Preserved Original Styling:
- Original CSS retained in packages/design-system/src/css/style.css
- Some generic controls (DropdownControl, SliderControl, ToggleControl)
preserved as-is
- Future migration to Tailwind and PrimeVue components is planned but
out of scope for this PR
Preserved Core Functionality:
- Drawing algorithms and brush rendering logic remain unchanged
- Pan/zoom calculations preserved
- Canvas operations (composite modes, image processing) unchanged
- Tool behaviors (brush, color select, paint bucket) identical
- No changes to mask generation or export logic
DO NOT Review:
- CSS styling choices (preserved from original)
- Drawing algorithm implementations (unchanged)
- Canvas rendering logic (ported as-is)
- UI/UX changes (none exist)
- Component library choices (future work)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6629-fully-refactor-mask-editor-into-vue-based-2a46d73d36508114ab8bd2984b4b54e4)
by [Unito](https://www.unito.io)
This pull request introduces a new extensible system for adding custom
action bar buttons to the top menu, and demonstrates its use by adding a
cloud feedback button. The changes include defining a new
`ActionBarButton` type, updating the extension system to support action
bar buttons, creating a Pinia store to aggregate buttons from
extensions, and updating the UI to render these buttons in the top menu.
The cloud feedback button is now conditionally loaded for cloud
environments.
**Extensible Action Bar Button System**
* Defined a new `ActionBarButton` interface in `comfy.ts` for describing
buttons (icon, label, tooltip, class, click handler) and added an
`actionBarButtons` property to the `ComfyExtension` interface to allow
extensions to contribute buttons.
[[1]](diffhunk://#diff-c29886a1b0c982c6fff3545af0ca
<img width="1134" height="437" alt="Screenshot 2025-11-07 at 01 07 36"
src="https://github.com/user-attachments/assets/36b6145a-0b82-4f7d-88e8-f2ea350a359b"
/>
8ec269876c2cf3948f867d08c14032c04d66R60-R82)
[[2]](diffhunk://#diff-c29886a1b0c982c6fff3545af0ca8ec269876c2cf3948f867d08c14032c04d66R128-R131)
* Created a Pinia store (`actionBarButtonStore.ts`) that collects all
action bar buttons from registered extensions for use in the UI.
**UI Integration**
* Added a new `ActionBarButtons.vue` component that renders all action
bar buttons using the store, and integrated it into the top menu section
(`TopMenuSection.vue`).
[[1]](diffhunk://#diff-d6820f57cde865083d515ac0c23e1ad09e6fbc6792d742ae948a1d3b772be473R1-R28)
[[2]](diffhunk://#diff-45dffe390927ed2d5ba2426a139c52adae28ce15f81821c88e7991944562074cR10)
[[3]](diffhunk://#diff-45dffe390927ed2d5ba2426a139c52adae28ce15f81821c88e7991944562074cR28)
**Cloud Feedback Button Extension**
* Implemented a new extension (`cloudFeedbackTopbarButton.ts`) that
registers a "Feedback" button opening the Zendesk feedback page, and
ensured it loads only in cloud environments.
[[1]](diffhunk://#diff-b84a6a0dfaca19fd77b3fae6999a40c3ab8d04ed22636fcdecc65b385a8e9a98R1-R24)
[[2]](diffhunk://#diff-236993d9e4213efe96d267c75c3292d32b93aa4dd6c3318d26a397e0ae56bc87R32)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6626-Cloud-feedback-Extension-2a46d73d36508170bd07f582ccfabb3c)
by [Unito](https://www.unito.io)
This PR cleans up the base-layer utilities so they no longer pull
renderer or workbench code. The renderer-only `isPrimitiveNode` guard
now lives in `src/renderer/utils/nodeTypeGuards.ts`, and the node
help/model/ordering helpers have moved into `src/workbench/utils`. All
affected services, stores, scripts, and tests were updated to import
from the new locations.
The idea is to reduce the number of Base→Renderer/Base→Workbench edges
(higher scoped base/common utils should not import from
renderer/workbench layers).
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6621-refactor-move-renderer-dependent-utils-into-workbench-scope-2a36d73d36508167aff0fc8a22202d7f)
by [Unito](https://www.unito.io)
This pull request introduces several improvements to Vue reactivity and
user experience in the graph node and widget system. The main focus is
on ensuring that changes to node and widget data reliably trigger
updates in Vue components, improving drag-and-drop support for nodes,
and enhancing widget value handling for better compatibility and
reactivity.
**Vue Reactivity Improvements:**
* In `useGraphNodeManager.ts`, node data updates now create a completely
new object and add a timestamp (`_updateTs`) to force Vue's reactivity
system to detect changes. Additionally, node data is re-set on the next
tick to guarantee component updates.
[[1]](diffhunk://#diff-f980db6f42cef913c3fe92669783b255d617e40b9ccef9a1ab9cc8e326ff1790L272-R280)
[[2]](diffhunk://#diff-f980db6f42cef913c3fe92669783b255d617e40b9ccef9a1ab9cc8e326ff1790R326-R335)
* Widget value composables (`useWidgetValue` and related helpers) now
accept either a direct value or a getter function for `modelValue`, and
always normalize it to a getter. Watches are updated to use this getter
for more reliable reactivity.
[[1]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911L13-R14)
[[2]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911R49-R57)
[[3]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911L82-R91)
[[4]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911L100-R104)
[[5]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911L117-R121)
[[6]](diffhunk://#diff-92dc3c8b09ab57105e400e115196aae645214f305685044f62edc3338afa0911L140-R144)
[[7]](diffhunk://#diff-0c43cefa9fb524ae86541c7ca851e97a22b3fd01f95795c83273c977be77468fL47-R47)
* In `useImageUploadWidget.ts`, widget value updates now use a new
array/object to ensure Vue detects the change, especially for batch
uploads.
**Drag-and-Drop Support for Nodes:**
* The `LGraphNode.vue` component adds drag-and-drop event handlers
(`dragover`, `dragleave`, `drop`) and visual feedback (`isDraggingOver`
state and highlight ring) for improved user experience when dragging
files onto nodes. Node callbacks (`onDragOver`, `onDragDrop`) are used
for custom validation and handling.
[[1]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2L26-R27)
[[2]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R47-R49)
[[3]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R482-R521)
**Widget and Audio Upload Handling:**
* In `uploadAudio.ts`, after uploading an audio file, the widget's
callback is manually triggered to ensure Vue nodes update. There is also
a commented-out call to mark the canvas as dirty for potential future
refresh logic.
[[1]](diffhunk://#diff-796b36f2cafb906a5e95b5750ca5ddc1bf57a304d4a022e0bdaee04b4ee5bbc4R61-R65)
[[2]](diffhunk://#diff-796b36f2cafb906a5e95b5750ca5ddc1bf57a304d4a022e0bdaee04b4ee5bbc4R190-R191)
These changes collectively improve the reliability and responsiveness of
UI updates in the graph node system, especially in scenarios involving
external updates, drag-and-drop interactions, and batch widget value
changes.
https://github.com/user-attachments/assets/8e3194c9-196c-4e13-ad0b-a32177f2d062
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6514-Drag-vuenodes-input-29e6d73d3650817da1b7ef96b61b752d)
by [Unito](https://www.unito.io)
## Summary
Deduplicates workflow run telemetry and keeps a single source of truth
for execution while retaining click analytics and attributing initiator
source.
- Keep execution tracking in one place via `trackWorkflowExecution()`
- Keep click analytics via `trackRunButton(...)`
- Attribute initiator with `trigger_source` = 'button' | 'keybinding' |
'legacy_ui'
- Remove pre‑tracking from keybindings to avoid double/triple counting
- Update legacy UI buttons to emit both click + execution events (they
bypass commands)
## Problem
PR #6499 added tracking at multiple layers:
1) Keybindings tracked via a dedicated method and then executed a
command
2) Menu items tracked via a dedicated method and then executed a command
3) Commands also tracked execution
Because these ultimately trigger the same command path, this produced
duplicate (sometimes triplicate) events per user action and made it hard
to attribute initiator precisely.
## Solution
- Remove redundant tracking from keybindings (and previous legacy menu
handler)
- Commands now emit both:
- `trackRunButton(...)` (click analytics, includes `trigger_source` when
provided)
- `trackWorkflowExecution()` (single execution start; includes the last
`trigger_source`)
- Legacy UI buttons (which call `app.queuePrompt(...)` directly) now
also emit both events with `trigger_source = 'legacy_ui'`
- Add `ExecutionTriggerSource` type and wire `trigger_source` through
provider so `EXECUTION_START` matches the most recent click intent
### Telemetry behavior after this change
- `RUN_BUTTON_CLICKED` (click analytics)
- Emitted when a run is initiated via:
- Button: `trigger_source = 'button'`
- Keybinding: `trigger_source = 'keybinding'`
- Legacy UI: `trigger_source = 'legacy_ui'`
- `EXECUTION_START` (execution lifecycle)
- Emitted once per run at start; includes `trigger_source` matched to
the click intent above
- Paired with `EXECUTION_SUCCESS` / `EXECUTION_ERROR` from execution
handlers
## Benefits
- ✅ Accurate counts by removing duplicated run events
- ✅ Clear initiator attribution (button vs keybinding vs legacy UI)
- ✅ Separation of “intent” (click) vs. “lifecycle” (execution)
- ✅ Simpler implementation and maintenance
## Files Changed (high level)
- `src/services/keybindingService.ts`: Route run commands with
`trigger_source = 'keybinding'`
- `src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue`: Send
`trigger_source = 'button'` to commands
- `src/scripts/ui.ts`: Legacy queue buttons emit `trackRunButton({
trigger_source: 'legacy_ui' })` and `trackWorkflowExecution()`
- `src/composables/useCoreCommands.ts`: Commands emit
`trackRunButton(...)` + `trackWorkflowExecution()`; accept telemetry
metadata
- `src/platform/telemetry/types.ts`: Add `ExecutionTriggerSource` and
optional `trigger_source` in click + execution payloads
- `src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts`:
Carry `trigger_source` from click → execution and reset after use
- `src/stores/commandStore.ts`: Allow commands to receive args (for
telemetry metadata)
- `src/extensions/core/groupNode.ts`: Adjust command function signatures
to new execute signature
## Related
- Reverts the multi‑event approach from #6499
- Keeps `trackWorkflowExecution()` as the canonical execution event
while preserving click analytics and initiator attribution with
`trackRunButton(...)`
┆Issue is synchronized with this Notion page by Unito
---------
Co-authored-by: Christian Byrne <c.byrne@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Summary
Fully Refactored the Load3D module to improve architecture and
maintainability by consolidating functionality into a
centralized composable pattern and simplifying component structure. and
support VueNodes system
Changes
- Architecture: Introduced new useLoad3d composable to centralize 3D
loading logic and state
management
- Component Simplification: Removed redundant components
(Load3DAnimation.vue, Load3DAnimationScene.vue,
PreviewManager.ts)
- Support VueNodes
- improve config store
- remove lineart output due Animation doesnot support it, may add it
back later
- remove Preview screen and keep scene in fixed ratio in load3d (not
affect preview3d)
- improve record video feature which will already record video by same
ratio as scene
Need BE change https://github.com/comfyanonymous/ComfyUI/pull/10025https://github.com/user-attachments/assets/9e038729-84a0-45ad-b0f2-11c57d7e0c9a
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5765-refactor-refactor-load3d-2796d73d365081728297cc486e2e9052)
by [Unito](https://www.unito.io)
This pull request refactors how export menu options are added to
3D-related nodes, updating the API to use a new `getNodeMenuItems` hook
and simplifying the menu item creation logic. The changes improve
consistency and maintainability across extensions handling 3D nodes, and
clarify the expected return types for menu item hooks.
**Refactoring and API updates for node menu items:**
* Replaced usage of the legacy `createExportMenuOptions` function with
the new `createExportMenuItems` function in `load3d.ts`, `saveMesh.ts`,
and related imports, aligning all 3D node extensions to the new API.
[[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L6-R12)
[[2]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43L4-R7)
* Introduced and implemented the `getNodeMenuItems` hook in the
extension registration for `Load3D`, `Preview3D`, and `SaveGLB` nodes,
ensuring export menu items are only shown for the appropriate node
types.
[[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3R293-R302)
[[2]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3R521-R530)
[[3]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43R48-R57)
* Removed assignment of `getExtraMenuOptions` to nodes, fully migrating
to the new menu item hook approach for context menus.
[[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L301-L302)
[[2]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L548-L549)
[[3]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43L64-L67)
**Menu item creation logic simplification:**
* Refactored `createExportMenuOptions` to `createExportMenuItems` in
`exportMenuHelper.ts`, changing it to directly return an array of menu
items (with separators) instead of a function, simplifying its usage and
reducing boilerplate.
[[1]](diffhunk://#diff-42404da1a87a52d304371a13d5f021bdad837765b94d86d186abb2d99a8cb707L14-R22)
[[2]](diffhunk://#diff-42404da1a87a52d304371a13d5f021bdad837765b94d86d186abb2d99a8cb707L59-R58)
**Type and documentation improvements:**
* Updated the `ComfyExtension` interface in `comfy.ts` to clarify that
menu item hooks (`getCanvasMenuItems`, `getNodeMenuItems`) now return
arrays that may include `null` values as separators, improving type
safety and documentation.
See before and after below
<img width="1459" height="897" alt="Screenshot 2025-10-30 at 07 08 04"
src="https://github.com/user-attachments/assets/ec4464c9-f733-4b4c-87c4-bb5060ccaa68"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6454-Complete-context-menu-migration-for-Load3D-Preview3D-and-SaveGLB-29c6d73d3650819995b4c4dc1582cd86)
by [Unito](https://www.unito.io)
This pull request refactors how context menu items are contributed by
extensions in the LiteGraph-based canvas. The legacy monkey-patching
approach for adding context menu options is replaced by a new, explicit
API (`getCanvasMenuItems` and `getNodeMenuItems`) for extensions. A
compatibility layer is added to support legacy extensions and warn
developers about deprecated usage. The changes improve maintainability,
extension interoperability, and migration to the new context menu
system.
### Context Menu System Refactor
* Introduced a new API for extensions to contribute context menu items
via `getCanvasMenuItems` and `getNodeMenuItems` methods, replacing
legacy monkey-patching of `LGraphCanvas.prototype.getCanvasMenuOptions`.
Major extension files (`groupNode.ts`, `groupOptions.ts`,
`nodeTemplates.ts`) now use this new API.
[[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917L1779-R1771)
[[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL232-R239)
[[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL447-R458)
* Added a compatibility layer (`legacyMenuCompat` in
`contextMenuCompat.ts`) to detect and warn when legacy monkey-patching
is used, and to extract legacy-added menu items for backward
compatibility.
[[1]](diffhunk://#diff-2b724cb107c04e290369fb927e2ae9fad03be9e617a7d4de2487deab89d0d018R2-R45)
[[2]](diffhunk://#diff-d3a8284ec16ae3f9512e33abe44ae653ed1aa45c9926485ef6270cc8d2b94ae6R1-R115)
### Extension Migration
* Refactored core extensions (`groupNode`, `groupOptions`, and
`nodeTemplates`) to implement the new context menu API, moving menu item
logic out of monkey-patched methods and into explicit extension methods.
[[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917L1633-L1683)
[[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL19-R77)
[[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL366-R373)
### Type and Import Cleanup
* Updated imports for context menu types (`IContextMenuValue`) across
affected files for consistency with the new API.
[[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917R4-L7)
[[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL1-R11)
[[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL2-R6)
[[4]](diffhunk://#diff-bde0dce9fe2403685d27b0e94a938c3d72824d02d01d1fd6167a0dddc6e585ddR10)
### Backward Compatibility and Migration Guidance
* The compatibility layer logs a deprecation warning to the console when
legacy monkey-patching is detected, helping developers migrate to the
new API.
---
These changes collectively modernize the context menu extension
mechanism, improve code clarity, and provide a migration path for legacy
extensions.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5993-Contextmenu-extension-migration-2876d73d3650813fae07c1141679637a)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Split mask editor structure into smaller files
## Changes
This PR is a prerequisite step for [the issue - refactoring the mask
editor using
Vue](https://github.com/Comfy-Org/ComfyUI_frontend/issues/5956). It
splits the current monolithic maskeditor.ts (about 5700 lines) into
separate files; otherwise, the original single file would be very
difficult to analyze or maintain.
This PR itself does not introduce any Vue, nor should it have any
functional changes or modifications. It's purely a code-level split,
with all related files placed in the maskeditor folder.
The original maskeditor.ts has been renamed to maskeditor.ts.backup for
future reference.
## Review Focus
Since this PR is only for splitting purposes, all logic remains
consistent with the original. Therefore, for any reviewer: any code
logic improvements should happen in the subsequent Vue-based
refactoring, not in this PR.
Following this PR, I will perform a Vue-based refactoring of the mask
editor to align with the frontend's overall Vue architecture and provide
better cloud-related support.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6308-refactor-Split-mask-editor-into-smaller-files-2986d73d36508131937dd43e465a47bd)
by [Unito](https://www.unito.io)
## Summary
Implements server-side remote configuration to decouple runtime behavior
from build artifacts, enabling dynamic configuration updates without
redeployment.
## Technical Changes
- **Replaced** build-time constants (`__MIXPANEL_TOKEN__`,
`__BUILD_FLAGS__`) with runtime configuration loaded from
`/api/features`
- Configuration now sourced from `window.__CONFIG__` (hydrated from
`/api/features` endpoint)
- **Added** `src/platform/remoteConfig/` service that polls server
configuration every 30 seconds
- **Modified** application bootstrap sequence in `main.ts` to load
remote config before module initialization (required for cloud builds)
- **Removed** global constants: `__BUILD_FLAGS__`, `__MIXPANEL_TOKEN__`.
Runtime subscription enforcement toggle via `subscription_required` flag
- Server health alerts with variant-based severity rendering
(info/warning/error) via topbar badges
## Rationale
- **Build-once-deploy-anywhere**: Single immutable artifact promoted
through environments (staging → production)
- **Zero-downtime configuration**: Update behavior without rebuilding or
redeploying the application
- **Incident response**: Disable features or display alerts dynamically
in response to outages or degraded service
- **Instant rollback**: Revert configuration changes server-side without
artifact redeployment
- **Progressive delivery**: Enable A/B testing, canary releases, and
user/region-based configuration
- **Environment parity**: Eliminate configuration drift between staging
and production builds
- Decouples deployment cadence from configuration changes
- Enables GitOps workflows for configuration management separate from
code deployments
- Supports real-time operational control of client behavior
- Reduces build matrix complexity (no per-environment builds)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6246-change-cloud-feature-flags-to-be-loaded-dynamically-at-runtime-rather-than-set-in-build-2966d73d3650811cbb41c9093961037a)
by [Unito](https://www.unito.io)
## Summary
Centralized all download functionalities across app. Then changed
downloadFile on the cloud distribution to stream assets via blob fetches
while desktop/local retains direct anchor downloads. This fixes issue
where trying to download cross-origin resources opens them in the
window, potentially losing the user's unsaved changes.
## Changes
- **What**: Moved `downloadBlob` into `downloadUtil`, routed all callers
(3D exporter, recording manager, node template export, workflow/palette
export, Litegraph save, ~~`useDownload` consumers~~) through shared
helpers, and changed `downloadFile` to `fetch` first when `isCloud` so
cross-origin URLs download reliably
- `useDownload` is the exception since we simply cannot do model
downloads through blob (forcing user to transfer the entire model data
twice is bad). Fortunately on cloud, the user doesn't need to download
models locally anyway.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6188-refactor-centralize-all-download-utils-across-app-and-apply-special-cloud-specific-behav-2946d73d365081de9f27f0994950511d)
by [Unito](https://www.unito.io)
Summary
Implements cloud subscription management UI and flow for ComfyUI Cloud
users.
Core Features:
- Subscription Status Tracking: Global reactive state management for
subscription status across all components
using shared subscriptionStatus ref
- Subscribe to Run Button: Replaces the Run button in the actionbar with
a "Subscribe to Run" button for users
without active subscriptions
- Subscription Required Dialog: Modal dialog with subscription benefits,
pricing, and checkout flow with video
background
- Subscription Settings Panel: New settings panel showing subscription
status, renewal date, and quick access to
billing management
- Auto-detection & Polling: Automatically polls subscription status
after checkout completion and syncs state
across the application
https://github.com/user-attachments/assets/f41b8e6a-5845-48a7-8169-3a6fc0d2e5c8
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6064-subscription-page-28d6d73d36508135a2a0fe7c94b40852)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Fixed the AUDIO_RECORD widget to display correctly in both rendering
modes:
- Removed conflicting AUDIO_RECORD registration from ComfyWidgets that
was blocking the custom widget implementation in uploadAudio.ts
extension
- Changed canvasOnly flags from true to false on both audioUIWidget and
recordWidget to enable Vue nodes rendering
- Added type override (recordWidget.type = 'audiorecord') after widget
creation to enable Vue component lookup while preserving LiteGraph
button rendering
- Removed unused IAudioRecordWidget type definition
The widget now works correctly:
- LiteGraph mode: Displays as a functional button
- Vue nodes mode: Displays full recording UI with waveform visualization
## 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 -->
https://github.com/user-attachments/assets/cf6513d4-0a4b-4210-88e2-a948855b5206
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6094-fix-Enable-AUDIO_RECORD-widget-in-both-LiteGraph-and-Vue-nodes-modes-28e6d73d365081faa879f53e3e2dddad)
by [Unito](https://www.unito.io)
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
This pull request refactors how top bar badges are handled in the
application, making badge rendering extensible and moving cloud badge
logic into the extension system. The main changes include replacing the
old `CloudBetaBadge` component with a new, generic badge system,
introducing a Pinia store for badge management, and updating the
extension API to support top bar badges.
**Badge System Refactor and Extensibility**
* Replaced the hardcoded `CloudBetaBadge` with a new `TopbarBadges`
component, which dynamically renders badges from the store instead of
relying on the `isCloud` flag in `TopMenubar.vue`.
[[1]](diffhunk://#diff-b7d7bf1028f09fb907c09edf27631214d005c93b80eaff7cf15cfd53671b1e8aL9-R9)
[[2]](diffhunk://#diff-b7d7bf1028f09fb907c09edf27631214d005c93b80eaff7cf15cfd53671b1e8aL43-R48)
* Renamed and refactored `CloudBetaBadge.vue` to `TopbarBadge.vue`,
making it accept a generic `badge` prop and removing i18n logic from the
component.
* Added a new `TopbarBadges.vue` component to render all badges from the
`topbarBadgeStore`.
**Badge Data Management**
* Introduced a new Pinia store `topbarBadgeStore` that aggregates top
bar badges from all extensions, enabling dynamic badge management.
**Extension System Integration**
* Updated the extension API (`ComfyExtension` interface) to support a
new `topbarBadges` property, allowing extensions to contribute badges to
the top bar.
* Added a core extension (`cloudBadge.ts`) that registers a "Comfy
Cloud" beta badge when running in a cloud environment, using the new
badge system.
[[1]](diffhunk://#diff-b7818ca9daae2411d56695777160b8132507f2a3ff4f700d2510453c8833ca75R1-R16)
[[2]](diffhunk://#diff-236993d9e4213efe96d267c75c3292d32b93aa4dd6c3318d26a397e0ae56bc87R2)
**Type Definitions**
* Added a new `TopbarBadge` type to `comfy.ts` to define the structure
for top bar badges, supporting optional labels.
Under some infrequent circumstances, `audioWidget.value` is not a
string. Presumably if a workflow is loaded with a saved file choice that
does not exist and the value is set to undefined instead.
Instead of a cast, a proper type guard is used and the widget is not
updated if the new value is not a string.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6041-Use-type-check-instead-of-cast-28b6d73d365081249353f4f905769f89)
by [Unito](https://www.unito.io)
This pull request introduces a new audio playback widget for node UIs
and integrates it into the node widget system. The main changes include
the implementation of the `WidgetAudioUI` component, its registration in
the widget registry, and updates to pass node data to the new widget.
Additionally, some logging was added for debugging purposes.
**Audio Widget Implementation and Integration:**
* Added a new `WidgetAudioUI.vue` component that provides audio playback
controls (play/pause, progress slider, volume, options) and loads audio
files from the server based on node data.
* Registered the new `WidgetAudioUI` component in the widget registry by
importing it and adding an entry for the `audioUI` type.
[[1]](diffhunk://#diff-c2a60954f7fdf638716fa1f83e437774d5250e9c99f3aa83c84a1c0e9cc5769bR21)
[[2]](diffhunk://#diff-c2a60954f7fdf638716fa1f83e437774d5250e9c99f3aa83c84a1c0e9cc5769bR112-R115)
* Updated `NodeWidgets.vue` to pass `nodeInfo` as the `node-data` prop
to widgets of type `audioUI`, enabling the widget to access
node-specific audio file information.
**Debugging and Logging:**
* Added logging of `nodeData` in `LGraphNode.vue` and
`WidgetAudioUI.vue` to help with debugging and understanding the data
structure.
[[1]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2R188-R189)
[[2]](diffhunk://#diff-71cce190d74c6b5359288857ab9917caededb8cdf1a7e6377578b78aa32be2fcR1-R284)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5627-Vuenodes-audio-widgets-2716d73d365081fbbc06c1e6cf4ebf4d)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Arjan Singh <1598641+arjansingh@users.noreply.github.com>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Extracts shared formatting and network utilities into dedicated
workspace package.
## Changes
- **What**: Created `@comfyorg/shared-frontend-utils` package containing
formatUtil and networkUtil
- **Breaking**: None - utilities remain accessible via path aliases in
`tsconfig`
Split `createAnnotatedPath` and `electronMirrorCheck` out and left in
frontend, due to their tightly-coupled nature. See [discussion on this
PR](https://github.com/Comfy-Org/ComfyUI_frontend/pull/5843#issuecomment-3344724727).
## Summary
Added `canvasOnly` flag to runtime-generated widgets to prevent Vue
renderer from displaying them while keeping canvas functionality intact.
## Changes
- **What**: Added `canvasOnly` widget option to hide upload, webcam, and
refresh widgets from Vue renderer
In the Canvas (LiteGraph) system, there was a small set of widgets with
strictly defined components. There, if we wanted some unique or
relatively complex behavior (like an upload butotn), we needed to create
a separate widget that would be coupled to the original widget at
runtime (and would not be serialized).
In the Vue renderer system, we can simply add flags to the inputSpec or
widget options and conditionally render complex UI additions -- i.e.,
there is no need for the hard-to-maintain runtime widget associations.
Expressing such things entirely in the view layer simplifies business
logic related to graph state, as we no longer need to account for
preserving the connections between runtime widgets and their special
siblings -- we also do not need to worry about the implications for
state serialization.
## Related
- https://github.com/Comfy-Org/ComfyUI_frontend/pull/5798
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5831-designate-canvasOnly-on-runtime-generated-virtual-widgets-so-they-are-hidden-in-Vue-ren-27c6d73d365081fb8641feec010190df)
by [Unito](https://www.unito.io)
---------
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Cleanup and fixes to the existing syncing logic.
## Review Focus
This is probably enough to review and test now.
Main things that should still work:
- moving nodes around
- adding new ones
- switching back and forth between Vue and Litegraph
Let me know if you find any bugs that weren't already present there.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5789-WIP-Litegraph-Vue-synchronization-work-27a6d73d3650811682cacacb82367b9e)
by [Unito](https://www.unito.io)