- Remove comfyPageFixture re-export from ComfyPage.ts
- Update all test imports to use comfyPageFixture from its own file
- Update README.md example to show correct import path
- Fixes: comfyPageFixture → LocalhostComfyPage → ComfyPage circular import
Circular dependency was caused by:
- comfyPageFixture.ts importing testComfySnapToGridGridSize from ComfyPage.ts
- ComfyPage.ts re-exporting comfyPageFixture
Solution:
- Extract testComfySnapToGridGridSize to constants.ts
- Both files import from constants.ts
- Only type-import ComfyPage in comfyPageFixture.ts
Now clean dependency graph with no cycles.
## Summary
1. Add a `getOptionLabel` option to `ComboWidget` so users of it can map
of custom labels to widget values. (e.g., `"My Photo" ->
"my_photo_1235.png"`).
2. Utilize this ability in Cloud environment to map user uploaded
filenames to their corresponding input asset.
3. Copious unit tests to make sure I didn't (AFAIK) break anything
during the refactoring portion of development.
4. Bonus: Scope model browser to only show in cloud distributions until
it's released elsewhere; should prevent some undesired UI behavior if a
user accidentally enables the assetAPI.
## Review Focus
Widget code: please double check the work there.
## Screenshots (if applicable)
https://github.com/user-attachments/assets/a94b3203-c87f-4285-b692-479996859a5a
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6585-Feat-input-mapping-2a26d73d365081faa667e49892c8d45a)
by [Unito](https://www.unito.io)
This pull request updates the design system color tokens and refactors
node and widget component styles throughout the codebase to use new,
more consistent CSS variables. The changes ensure that node and widget
components are styled using unified design tokens, improving
maintainability and theme support for both light and dark modes.
**Design System Token Updates**
* Added new component and node-related CSS variables for background,
border, foreground, and widget states in both light and dark themes in
`style.css`.
[[1]](diffhunk://#diff-71b6b57a56095b04e47c797a5016149b76b27971cab04b93f033f1f846e0f5a0R246-R256)
[[2]](diffhunk://#diff-71b6b57a56095b04e47c797a5016149b76b27971cab04b93f033f1f846e0f5a0R354-R364)
* Introduced `--color-graphite-400` and adjusted several existing color
assignments for better palette consistency.
[[1]](diffhunk://#diff-71b6b57a56095b04e47c797a5016149b76b27971cab04b93f033f1f846e0f5a0R76)
[[2]](diffhunk://#diff-71b6b57a56095b04e47c797a5016149b76b27971cab04b93f033f1f846e0f5a0L304-R316)
* Updated semantic CSS variables to reference the new component/node
tokens for easier usage in components.
* Changed `--secondary-background-hover` to match
`--secondary-background` for improved hover consistency.
**Component Refactoring: Node and Widget Styles**
* Refactored Vue component classes and inline styles to use the new CSS
variables for node backgrounds, borders, and widget states, replacing
legacy variables like `bg-node-component-surface` and
`border-node-component-border` with `bg-component-node-background` and
`border-component-node-border`.
[[1]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2L11-R14)
[[2]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2L39-R39)
[[3]](diffhunk://#diff-a7744614cf842e54416047326db79ad81f7c7ab7bfb66ae2b46f5c73ac7d47f2L384-R384)
[[4]](diffhunk://#diff-19537a67677431ecdc9aec43877d28814e37edf0e45b0b0b484ea08832cad299L5-R13)
* Updated widget dropdowns, select, and input components to use
`text-component-node-foreground-secondary` for icons and foregrounds,
and new background variables for buttons and inputs.
[[1]](diffhunk://#diff-489229f88dfdfd5d883a3ef7fad6effa0790a18a831d5a9d84642dfb246962a2L29-R29)
[[2]](diffhunk://#diff-489229f88dfdfd5d883a3ef7fad6effa0790a18a831d5a9d84642dfb246962a2L100-R100)
[[3]](diffhunk://#diff-661a09de2721335e118a693b25d09922ada0ccbd0a51284691ed784fbe18874eL13-R13)
[[4]](diffhunk://#diff-2856391d03b0d38db1ed922b5034a05bc32e978c51f8175057d84cf82399d986L13-R13)
[[5]](diffhunk://#diff-4ee47848821aff71b6da0a1bb7fb8976e7879d706f71ff2ab3c5b046f5ef528cL10-R10)
[[6]](diffhunk://#diff-8b7ed2ce6194a262fb1e950294699cb8722630920362143a765802b602ae5fc8L106-R113)
[[7]](diffhunk://#diff-8b7ed2ce6194a262fb1e950294699cb8722630920362143a765802b602ae5fc8L119-R123)
[[8]](diffhunk://#diff-597a77456bf4b0c2d390fc46a930f37156b2f26ca030259b6703e5d39ff6b20eL37-R53)
[[9]](diffhunk://#diff-29348fa2e5b8cec1301a99bdec241379aeefc1747cceeb0c39b7df452ca635ffL7-R7)
**Service Layer Updates**
* Updated the color palette service mapping to use the new CSS variable
names for node and widget colors, ensuring consistency across the
application.
*
https://github.com/user-attachments/assets/d9535f9a-b459-49bf-b2fe-ed872916fa4e
These changes collectively modernize the styling approach for node and
widget components, making it easier to maintain and extend theme
support.
---------
Co-authored-by: github-actions <github-actions@github.com>
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
- Replace hardcoded `<base href="/">` in index.html with dynamic vite
base config
- Set `base: DISTRIBUTION === 'cloud' ? '/' : ''` in vite.config.mts
- Ensures proper asset loading across different deployment contexts
## Test plan
- [ ] Verify cloud distribution builds work correctly
- [ ] Verify localhost/desktop distributions work correctly
- [ ] Test asset loading in both contexts
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6562-fix-vite-use-dynamic-base-URL-based-on-cloud-distribution-2a06d73d365081c8b5d2e58870ebd14d)
by [Unito](https://www.unito.io)
## Summary
- Resolves multiple cloud environment issues when accessing
`/cloud/user-check` directly
- Fixes API routing problems that caused 304 Not Modified errors and
JSON parsing failures
- Maintains compatibility with subpath deployments for OSS users
## Root Cause
The `api_base` was incorrectly calculated as `/cloud` on cloud SPA
routes, causing API calls to use wrong paths like `/cloud/api/user`
instead of `/api/user`.
## Issues Fixed
- ❌ `/cloud/user-check` direct access resulted in infinite loading
- ❌ JSON parsing errors: `Unexpected token '<', "<!DOCTYPE "... is not
valid JSON`
- ❌ 304 Not Modified responses on `/cloud/api/user`,
`/cloud/api/settings/onboarding_survey`, `/cloud/api/system_stats`
## Solution
Added conditional `api_base` calculation in `ComfyApi` constructor:
- **Cloud SPA routes** (`/cloud/*`): Use empty `api_base` → API calls
use `/api/*` paths
- **Regular deployments**: Keep existing logic → Supports subpath
deployments
## Test Plan
- [x] Verify `/cloud/user-check` direct access works without infinite
loading
- [x] Verify all API calls return 200 instead of 304
- [x] Verify OSS subpath deployment compatibility unchanged
- [x] Test authentication flow completion
🤖 Generated with [Claude Code](https://claude.ai/code)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6572-Fix-cloud-routing-issues-caused-by-incorrect-api_base-calculation-2a16d73d36508163aeb2cbf6347b427d)
by [Unito](https://www.unito.io)
## Summary
- Add meta tags plugin for social media previews (Twitter, Facebook,
LinkedIn, etc.)
- Include SEO meta tags (title, description, keywords)
- Only applies to cloud distribution (`DISTRIBUTION === 'cloud'`)
## Changes
- Added Open Graph meta tags for better social media link previews
- Added Twitter Card meta tags for Twitter sharing
- Added SEO meta tags (title, description, keywords)
- Added `og-image.png` for preview image
- Meta tags only inject when `DISTRIBUTION === 'cloud'` to avoid
affecting other distributions
## Test plan
- [x] Build with `DISTRIBUTION=cloud pnpm build`
- [x] Verify meta tags appear in built HTML
- [ ] Test link sharing on Twitter, Facebook, Slack
- [ ] Verify meta tags don't appear in localhost/desktop builds
🤖 Generated with Claude Code
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6571-feat-Add-Open-Graph-and-Twitter-meta-tags-for-cloud-distribution-2a16d73d365081e58c73f6009513b2bb)
by [Unito](https://www.unito.io)
## Summary
Adds support for loading templates via URL query parameters. Users can
now share direct links to templates.
To test:
1. checkout this branch
2. start dev server on port 5173
3. go to http://localhost:5173/?template=image_qwen_image_edit_2509
**Examples:**
- `/?template=default` - Loads template with default source
- `/?template=flux_simple&source=custom` - Loads from custom source
Includes error handling with toast notifications for invalid templates
and comprehensive test coverage.
---------
Co-authored-by: Christian Byrne <c.byrne@comfy.org>
This PR adds a 'Partner Nodes' virtual category that filters templates
where OpenSource === false, and renames the 'License' filter to 'Runs
on' with values 'ComfyUI' and 'Partner API'. The implementation is
backward compatible and works like the existing 'Basics' category - it
filters templates from any category without duplication. The filter
logic now uses the explicit OpenSource field instead of heuristic
detection. This change coordinates with upcoming workflow_templates repo
updates that will move API templates to GENERATION TYPE categories and
add the OpenSource field to all API node templates.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6542-feat-Add-Partner-Nodes-virtual-category-and-rename-license-filter-29f6d73d36508111a85bdf5017f0a100)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## 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
Renamed the templates license filter to better reflect its actual
purpose - showing where a template executes (locally in ComfyUI vs
external/remote API).
The current "License" filter has been causing confusion with model
licensing terms (e.g., Apache vs flux-dev licensing). This PR clarifies
the filter's purpose by renaming it to "Runs On" and updating the
options to be more descriptive of inference location.
<img width="196" height="230" alt="image"
src="https://github.com/user-attachments/assets/8cbea263-f399-4945-82c1-357ec185f5a7"
/>
<img width="861" height="597" alt="image"
src="https://github.com/user-attachments/assets/af116876-d7a5-49c5-b791-1fda637ff3a3"
/>
## Changes
- **Filter name**: "License" → "Runs On"
- **Filter options**:
- "Open Source" → "ComfyUI"
- "Closed Source (API Nodes)" → "External or Remote API"
- **Icon**: Changed from `file-text` to `server` for better visual
representation
- **Variable naming**: Updated all related variables, types, and tests
to use `runsOn` naming convention
- **Telemetry**: Updated metadata to track `selected_runs_on` instead of
`selected_licenses`
## Why "Runs On"?
- **Clear intent**: Users want to know if a template runs locally or
requires an API call
- **Avoids confusion**: Separates the concept from model licensing terms
- **Inclusive wording**: "Remote" is included alongside "API" to help
users who may not be familiar with API terminology
- **Cloud-agnostic**: "Runs On" works whether the app itself is running
locally or in the cloud
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6543-feat-Rename-license-filter-to-Runs-On-filter-in-template-selector-29f6d73d3650811f935bc1f3fce7d7ad)
by [Unito](https://www.unito.io)
## Summary
- Reorganized media asset card layout for improved UX
- Moved duration/format chips to top-left (visible in default state)
- Replaced multiple action buttons with zoom + more menu pattern
- Repositioned output count to top-right for better visibility
## Changes
1. **Duration & format chips**: Moved from bottom-left to top-left,
shown when card is not hovered and media is not playing
2. **Media actions**: Simplified to zoom button + more menu (contains
delete, download options), shown on hover or during playback
3. **Output count**: Relocated from bottom-right to top-right for
consistent positioning
## Test plan
- [x] Verify duration/format chips appear in top-left when card is idle
- [x] Confirm action buttons (zoom + more) appear on hover
- [x] Check output count displays correctly in top-right
- [x] Test transitions between hover/non-hover states
- [x] Verify media playback doesn't interfere with UI elements
🤖 Generated with [Claude Code](https://claude.ai/code)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6541-UI-Redesign-media-asset-card-layout-29f6d73d365081eb8614d5dc9b2dc214)
by [Unito](https://www.unito.io)
Removes the wait-for-ci job that has been causing random workflow skips
and reliability issues due to race conditions with CI check creation.
Multiple attempts to fix timing issues have resulted in ongoing edge
cases, so this simplifies the workflow to run immediately when the
claude-review label is added. Reviews can now run in parallel with CI
checks, and while they may occasionally run on PRs with failing CI, the
review feedback is often valuable regardless of CI status.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6548-fix-Remove-unreliable-CI-wait-logic-from-Claude-review-workflow-29f6d73d36508125910ef4feba5abdf4)
by [Unito](https://www.unito.io)
## Problem
401 authentication errors were persistently occurring when calling
log-related APIs in the Cloud environment.
## Root Cause
- Frontend was calling `/internal/logs/*` endpoints in all environments
- Cloud backend provides public APIs at `/api/logs/*` (no authentication
required)
- OSS (open source) backend uses `/internal/logs/*` paths
- This caused Cloud to call non-existent paths → resulting in 401 errors
## Solution
Modified to use appropriate API endpoints based on environment using the
`isCloud` flag:
- Cloud environment: Use `/api/logs/*`
- OSS environment: Use `/internal/logs/*`
## Changes
- `getLogs()`: Added environment-specific URL branching
- `getRawLogs()`: Added environment-specific URL branching
- `subscribeLogs()`: Added environment-specific URL branching
## Testing
- [x] Verified log functionality works correctly in local (OSS)
environment
- [x] Confirmed 401 errors are resolved in Cloud environment
## Related Issues
This resolves the 401 errors tracked in Sentry for log API endpoints.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6539-fix-Use-environment-specific-log-API-endpoints-for-Cloud-and-OSS-29f6d73d365081da9e77f8b55556ca81)
by [Unito](https://www.unito.io)
## Summary
Removes unsafe `as any` type assertions from subscription credits
composable now that the OpenAPI spec has been updated with the missing
balance breakdown fields.
The `GetCustomerBalance` API response now includes:
- `cloud_credit_balance_micros` - Monthly subscription credits
- `prepaid_balance_micros` - Pre-paid top-up credits
These fields were previously accessed with `as any` because they weren't
in the TypeScript type definitions. With the OpenAPI spec update (PR
#6531), these fields are now properly typed.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6536-fix-remove-unsafe-type-assertions-in-subscription-credits-29f6d73d365081ffae52cc85c01c139b)
by [Unito](https://www.unito.io)
Co-authored-by: Christian Byrne <c.byrne@comfy.org>