mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
858fe2385914842413402cdf8a8c60b3dfbe0933
1776 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
285421a87c |
feat: add queue progress overlay feature survey (#11560)
*PR Created by the Glary-Bot Agent* --- ## Summary Registers a new nightly feature survey for the Queue Progress Overlay using the existing feature-survey registry (same pattern as the merged node-search survey, PRs #8175/#8355/#9934). - New registry entry `queue-progress-overlay` → Typeform `HZ5saxry`, threshold **16**, 5s display delay. - `trackFeatureUsed()` wired at the major user-initiated handlers inside the overlay so the survey triggers regardless of panel location (floating-right v1 or docked-left v2). - Run button and other ActionBar items that the overlay pops over from are deliberately **not** tracked — tracking is scoped to interactions that originate inside the job panel / queue progress overlay itself. ## Tracked interactions Both variants share most sub-components, so tracking is instrumented once at each logical surface: - **`QueueProgressOverlay.vue`** (v1 container): `viewAllJobs`, `interruptAll`, `cancelQueuedWorkflows`, `onClearHistoryFromMenu`, `toggleAssetsSidebar`, `onCancelItem`, `onDeleteItem`, `inspectJobAsset` - **`QueueOverlayExpanded.vue`**: job tab switches - **`JobHistorySidebarTab.vue`** (v2 docked): job tab switches, `clearQueuedWorkflows`, `onClearHistory`, `onCancelItem`, `onDeleteItem`, `onViewItem` - **`JobFilterActions.vue`** (shared): workflow filter + sort mode selections - **`JobHistoryActionsMenu.vue`** (shared): docked-history toggle + run-progress-bar toggle Deliberately **not tracked** to keep the signal clean: - Hover handlers (ambient preview behaviour) - Search-box keystrokes (debounced typing) - Context menu open and menu-item dispatch — menu actions either bubble through already-tracked terminal handlers (e.g. inspect-asset → `onViewItem`) or are secondary operations (copy-id, open-workflow, download). Avoids double-counting per code review feedback. ## How it works (inherits from existing infrastructure) 1. `surveyRegistry.ts` drives `NightlySurveyController` → `NightlySurveyPopover`, which handles the Typeform embed. 2. Eligibility already gated on `isNightly && !isCloud && !isDesktop`, once-per-user, 4-day global cooldown across all surveys, and opt-out. 3. Typeform response routing to #C0ALLT6Q3SQ is handled on the Typeform side. ## Verification - `pnpm typecheck` ✅ - `pnpm lint` ✅ (no new warnings) - `pnpm knip` ✅ - `pnpm test:unit` on `src/components/queue`, `src/components/sidebar/tabs/JobHistorySidebarTab`, `src/platform/surveys` → **123/123 passing** - Pre-commit hooks (stylelint, oxfmt, oxlint, eslint, typecheck) all pass - Manual: dev server + backend boot cleanly, app loads without new runtime errors, `localStorage['Comfy.FeatureUsage']` layout verified to match what `useFeatureUsageTracker` writes ## Notes - Survey key `queue-progress-overlay` covers both v1 (floating-right) and v2 (docked-sidebar) per product guidance: _"This should trigger regardless of the location of the panel (docked from left or floating on right)."_ Both surfaces are the same product feature — the survey is intentionally scoped to the whole job-panel experience. ## Screenshots  ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11560-feat-add-queue-progress-overlay-feature-survey-34b6d73d3650819a9a50fd67fd9b5941) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
af70d88860 |
fix: keep finished badge fully opaque in ProgressToastItem (#11542)
## Summary - fix **[slack](https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1776801170742579)** - Move `opacity-50` off the row container onto the asset-name column only, so the contrast badge (white pill, dark label) is not dimmed to gray-on-gray when a download completes. - Matches the Figma intent that the `FINISHED` badge stands out — designer spec uses `base/foreground` for pill, `base/background` for text, which is unreadable when the parent is 50% opacity. <img width="560" height="269" alt="Screenshot 2026-04-23 at 2 46 17 PM" src="https://github.com/user-attachments/assets/fb84aa57-c348-4a86-9a65-9342c12400e1" /> <img width="764" height="332" alt="Screenshot 2026-04-23 at 2 46 41 PM" src="https://github.com/user-attachments/assets/ecbe6a5f-c2e8-4427-9c1d-f8f123009d2e" /> ## Before / After  ## Repro Cloud → trigger a model download → wait for completion → the `FINISHED` badge is the same tone as the toast surface (see Slack thread screenshots). ## Test plan - [ ] Complete a model download in cloud and confirm the `FINISHED` badge is clearly legible in both themes. - [ ] File name + subtitle still appear dimmed to signal the row is completed. - [ ] Running / failed / pending states unchanged. - Fixes [FE-237](https://linear.app/comfyorg/issue/FE-237/fix-honeytoast-badge-text-color-for-finished-job-matches-background) |
||
|
|
e4e1546458 |
test: add queue notification banners lifecycle browser tests (#11366)
*PR Created by the Glary-Bot Agent* --- ## Summary - Adds `browser_tests/tests/queueNotificationBanners.spec.ts` covering `useQueueNotificationBanners` composable E2E behavior - Adds `data-testid="queue-notification-banner"` to `QueueNotificationBannerHost.vue` for stable test targeting - Registers the new test ID in `TestIds.queue.notificationBanner` ### Test coverage added (7 tests) | Group | Tests | Behavior | |---|---|---| | Queuing lifecycle | 4 | `promptQueueing` → banner appears, `promptQueued` upgrades to queued, batch plural text, requestId mismatch doesn't upgrade | | Auto-dismiss | 1 | Banner disappears after 4s timeout | | FIFO queue | 1 | Second notification shows after first auto-dismisses | | Direct queued | 1 | `promptQueued` without prior `promptQueueing` shows banner directly | ### Approach Tests dispatch `promptQueueing`/`promptQueued` custom events directly via `window.app.api.dispatchCustomEvent()` inside `page.evaluate()`, matching how `app.ts` triggers these events during real queue operations. This avoids needing a running execution pipeline while exercising the full composable → component → DOM rendering chain. ### Verification - TypeScript: zero errors - ESLint: clean - oxlint: clean - oxfmt: formatted - Playwright execution requires running ComfyUI backend (not available in sandbox) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11366-test-add-queue-notification-banners-lifecycle-browser-tests-3466d73d36508172a7ffd3fe3b4fd925) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
004530b23a |
fix: search bar layout and autocomplete clipping on Desktop at small sizes (#11713)
## Summary
Fixes two visual bugs in the Desktop app at small window sizes: the
search bar getting pushed/clipped in modal headers, and autocomplete
suggestion dropdowns being cut off by `overflow-hidden` ancestors.
## Changes
- **`SearchAutocomplete.vue`**: Wrap `ComboboxContent` in
`ComboboxPortal` so the suggestions dropdown teleports to `<body>`,
escaping `overflow-hidden` ancestors (fixes z-index clipping in Manager
dialog and other modals using `BaseModalLayout`)
- **`BaseModalLayout.vue`**: Replace `shrink-0` with `min-w-0` on the
header content container so the search bar can shrink at narrow window
sizes instead of overflowing and being clipped by the modal root's
`overflow-hidden`
- **`GraphCanvas.vue`**: Fix dead code where the native drag
(`app-drag`) div was nested inside a `v-if="workflowTabsPosition ===
'Topbar'"` block with its own mutually exclusive condition — move it
before the block and add `pointer-events-auto` so Desktop window
dragging works when tabs are in Sidebar position
## Why no E2E tests
- **`SearchAutocomplete` portal**: The fix is structural (teleport to
`<body>`). A meaningful regression test would require opening the
Manager dialog with a real or mocked extension list — that is a
substantial standalone effort tracked in #11714.
- **`BaseModalLayout` header shrink**: A viewport-resize assertion would
test CSS layout behaviour, not application logic; it would be fragile
and low-value.
- **`GraphCanvas` app-drag**: Desktop/Electron-only.
`-webkit-app-region: drag` cannot be exercised in headless Chromium.
Unit tests for `SearchAutocomplete` cover the new code paths (portal
rendering, suggestion display, item selection).
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk UI-only changes: adjusts layout CSS and combobox rendering
via `ComboboxPortal`, plus adds unit tests; no business logic or data
flow changes.
>
> **Overview**
> Fixes small-window Desktop UI issues where modal-header search inputs
could be clipped and autocomplete dropdowns could be cut off by
`overflow-hidden` ancestors.
>
> `SearchAutocomplete` now renders its suggestions list inside a
`ComboboxPortal` (teleporting the popper content outside clipping
containers) and adds a focused unit test suite covering empty/non-empty
suggestions, selection behavior, and `optionLabel` handling.
>
> `BaseModalLayout` tweaks header flexbox constraints (`min-w-0` on the
header content container) to allow the search bar to shrink instead of
overflowing.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
dafb944c3b |
refactor: replace PrimeVue InputText/Textarea with project UI components (#11324)
## Summary
Fix part of the
#https://github.com/Comfy-Org/ComfyUI_frontend/issues/11092
A total of 4 `// @ts-expect-error` directives were removed across 3
files — all caused by PrimeVue's legacy `$el` access pattern (`const
inputElement = inputRef.value.$el`) — by replacing PrimeVue with
Reka-based UI components. 1 corresponding unit test file was added.
## Changes
### `src/components/ui/input/Input.vue`
- **Exposed APIs**: Extended `defineExpose` to include `blur()` and
`setSelectionRange()`. This allows parent components to programmatically
control input behavior without direct DOM manipulation.
### `src/components/ui/textarea/Textarea.vue`
- **Exposed APIs**: Added `focus()` via `defineExpose`.
- **Cleanup**: Removed redundant attribute spreading (`...restAttrs`) to
lean on Vue’s default `$attrs` inheritance, making the component more
predictable.
---
## Refactored Feature Components
### `WidgetMarkdown.vue` (Note/Markdown Widgets)
- **Dependency Swap**: Replaced `primevue/textarea` with local
`Textarea.vue`.
- **Logic Simplification**: Simplified focus logic from
`textareaRef.value?.$el?.focus()` to a typed
`textareaRef.value?.focus()`.
- **Code Style**: Converted arrow functions to function declarations and
removed redundant section comments.
### `PromptDialogContent.vue` (Generic Prompt Dialogs)
- **Component Update**: Replaced PrimeVue `FloatLabel` and `InputText`
with a native `<label>` and local `Input.vue`.
- **Vue 3.5 Adoption**: Implemented **Reactive Destructuring** for
props.
- **Conflict Resolution**: Renamed internal `onConfirm` handler to
`handleConfirm` to prevent collision with destructured props.
### `EditableText.vue` (Node Titles & Sidebar Items)
- **Style Modernization**: Removed `<style scoped>` block in favor of
**Tailwind CSS** utility classes (e.g., `inline`, `w-full`).
- **Clean Implementation**: Replaced PrimeVue PassThrough (`:pt`) logic
with standard `@blur` and `v-bind` attributes.
---
## Testing & Quality Assurance
### Updated Tests
- **Redundancy Removal**: Cleaned up `EditableText.test.ts` and
`WidgetMarkdown.test.ts` by removing unused PrimeVue global
registrations. All 34 existing behavioral tests remain passing.
### New Coverage
- **`PromptDialogContent.test.ts`**: Added 3 new tests to verify:
1. Correct initialization with `defaultValue`.
2. Value persistence when clicking the Confirm button.
3. Form submission via the `Enter` key.
---
## Manual Test Screenshot
All functions have passed testing.
<img width="594" height="530" alt="test5"
src="https://github.com/user-attachments/assets/46a6b3b2-1855-414e-ac78-65668052ce50"
/>
<img width="1190" height="1074" alt="test4"
src="https://github.com/user-attachments/assets/89aa61ab-9401-44c2-9eae-9ca8761df675"
/>
<img width="1154" height="1028" alt="test3"
src="https://github.com/user-attachments/assets/3f63cfdf-8fbd-4dd3-9e42-dbebe4d8d421"
/>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Moderate risk because it swaps underlying input/textarea components
and ref handling (focus/blur/selection) in interactive UI paths
(editable labels, prompt dialogs, markdown editor), which could subtly
change keyboard/blur behavior.
>
> **Overview**
> Refactors several Vue components to stop using PrimeVue
`InputText`/`Textarea` (and `$el` access) in favor of the project’s
`Input`/`Textarea` components, updating bindings/events and Tailwind
classes accordingly.
>
> Extends the shared `Input` to expose `blur`, `setSelectionRange`, and
`selectAll`, and updates `Textarea` to expose `focus`, enabling callers
to manage focus/selection without DOM internals.
>
> Adds a new unit test suite for `PromptDialogContent` and simplifies
existing tests by removing PrimeVue plugin/component setup; the groups
e2e test replaces a screenshot assertion with a functional visibility
check for the new title input.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
9384beaec6 |
test: Add tests for bounding box widget (#11343)
## Summary Adds coverage for the bounding box widget ## Changes - **What**: - Validates user interactions and functionality on the widget ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11343-test-Add-tests-for-bounding-box-widget-3456d73d365081eb8d03f2220a837816) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <cbyrne@comfy.org> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
ef98ba0e8f |
feat: add plum/ink color primitives and standardize design tokens (#11139)
*PR Created by the Glary-Bot Agent* --- ## Summary Adds new `plum` and `ink` color scales for Comfy Hub branding and standardizes existing tokens to align with current Figma design system. ### Changes **Phase 1 — New primitives** (`_palette.css`) - Added `plum-300/400/500/600` and `ink-100` through `ink-900` **Phase 2 — Token cleanup** (`style.css`) - Removed deprecated `slate-100/200/300` primitives (cool blue-grey, removed from Figma) - Removed duplicate `graphite-400` (identical hex to slate-100) - Dark mode: migrated 6 slate/graphite references to muted-foreground, smoke-700, smoke-800, charcoal-200 - Light mode: replaced 3 `ash-500` references with `smoke-800` per designer alignment ### Token migration detail | Dark mode token | Old value | New value | Rationale | |---|---|---|---| | `--node-component-header-icon` | slate-300 (#5b5e7d) | muted-foreground (smoke-800) | Figma `node/foreground-secondary` | | `--node-component-slot-text` | slate-200 (#9fa2bd) | smoke-700 (#a0a0a0) | Lighter neutral for text contrast | | `--node-component-surface-highlight` | slate-100 (#9c9eab) | smoke-800 (#8a8a8a) | Neutral grey highlight | | `--node-component-tooltip-border` | slate-300 (#5b5e7d) | charcoal-200 (#494a50) | Consistent with dark border tokens | | `--text-secondary` | slate-100 (#9c9eab) | smoke-700 (#a0a0a0) | Adequate contrast on dark surfaces | | `--widget-background-highlighted` | graphite-400 (#9c9eab) | smoke-800 (#8a8a8a) | Removed duplicate, neutral replacement | ### Visual note These changes shift some dark mode colors from cool blue-grey to neutral grey. This is intentional per the design team. The `--node-component-surface-highlight` and `--node-component-tooltip-border` tokens should be QA'd as the designer noted. ### Not included (Phase 3) Hub Dark overlay theme will ship separately once the Hub UI work is ready to validate against. ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11139-feat-add-plum-ink-color-primitives-and-standardize-design-tokens-33e6d73d365081418e13e0efe6161fb5) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
8f9f452c86 |
fix: enable Chrome password autofill on signup form (#11636)
## Summary Add `name` attributes to the signup form's email, password, and confirm-password inputs so Chrome's password manager recognizes the form and offers autofill/save. ## Changes - **What**: Pass `name` through to the underlying `<input>` on the email field (via `pt:root:name`) and on both password fields (via `pt:pc-input-text:root:name`). Without `name`, Chrome can't pair the email with the password and won't surface the save-password / suggest-strong-password prompts. ## Review Focus - The PrimeVue passthrough syntax (`pt:root:*` for `InputText`, `pt:pc-input-text:root:*` for `Password`) lands the attribute on the actual `<input>` element — verified in DevTools. - `confirm-password` is not a standard `autocomplete` token; we keep `autocomplete="new-password"` on both password fields and only differentiate via `name`. ## Screenshots (if applicable) https://github.com/user-attachments/assets/e1e25ab5-8496-4c84-b5f1-630f31956c80 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11636-fix-enable-Chrome-password-autofill-on-signup-form-34e6d73d36508180882cc9ebafb58417) by [Unito](https://www.unito.io) |
||
|
|
23e48b2140 |
feat: Node search - Improve category tree on mobile with collapse (#11687)
## Summary Improves the search experience on mobile by collapsing the category menu & reogranises the filer buttons ## Changes - **What**: - add toggle button to collapse category selection - auto collapse on mobile - floating panel on mobile - re-order filter buttons - tests ## Screenshots (if applicable) Closed: <img width="415" height="373" alt="image" src="https://github.com/user-attachments/assets/c99cd6cd-eb92-4ce3-9844-591dd1e80769" /> Desktop open: <img width="455" height="328" alt="image" src="https://github.com/user-attachments/assets/df15bdda-f77a-4c12-90e1-8608d67c55b4" /> Mobile open: <img width="427" height="600" alt="image" src="https://github.com/user-attachments/assets/a2b115ad-bce0-4ed1-9d30-126a35263259" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11687-feat-Node-search-Improve-category-tree-on-mobile-with-collapse-34f6d73d365081729075e8b0071a3bc1) by [Unito](https://www.unito.io) |
||
|
|
818e549e8e |
fix: hide blueprint node id in search (#11759)
There is a setting that enables Node IDs to display on the search results. Subgraphs have long non-user friendly IDs which cause this to render badly for the built in blueprints (and user published). This update hides the IDs for blueprint nodes. ## Changes - **What**: - hide if blueprint - add test ## Screenshots (if applicable) Before: <img width="910" height="504" alt="image" src="https://github.com/user-attachments/assets/9eea9fd7-8f72-4e1b-9522-46efba0ef71a" /> After: <img width="797" height="552" alt="image" src="https://github.com/user-attachments/assets/43d6fc62-4102-41c3-b9bb-a3efd244580d" /> ┆Issue is synchronized with this [Notion page](https://app.notion.com/p/PR-11759-fix-hide-blueprint-node-id-in-search-3516d73d365081baa055d12c6a31fadd) by [Unito](https://www.unito.io) |
||
|
|
b3f5f82216 |
Remove unused queue job components (#11621)
## Summary Remove the unused legacy queue job row implementation before changing the live queue popover behavior. ## Changes - Deleted `JobGroupsList` and its test. - Deleted `QueueJobItem` and its Storybook story. - Deleted `QueueAssetPreview`, which was only used by `QueueJobItem`. ## Review Focus - This PR is deletion-only. - `rg` found no remaining references to these components. - `knip` and `typecheck` pass. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11621-Remove-unused-queue-job-components-34d6d73d36508164bf32cb581594cd9f) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
9df4e02189 |
refactor: unify media asset downloads (#11717)
## Summary Unifies media asset download actions behind a single `downloadAssets(assets?)` API to avoid single and multi asset download path drift. ## Changes - **What**: Replaces `downloadAsset` and `downloadMultipleAssets` with `downloadAssets`, preserving no-arg media context fallback and explicit asset arrays. - **Dependencies**: None. ## Review Focus Download behavior for single-card, context-menu, and sidebar bulk actions should continue to use the same ZIP-export path for cloud multi-output jobs. Fixes #11715 ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11717-refactor-unify-media-asset-downloads-3506d73d3650810d8bcec9c0194e743d) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
1c541d8577 |
Short circuit asset reuploads, simplify node dnd (#11691)
When an output is dragged from the assets panel onto a node, outputs were being reuploaded. This logic has been simplified to instead reference the existing asset by resolving the annotated path. As part of this change, async drop handlers on nodes are also fixed. Rather than placing obligation of event handling on client code, not respecting async handlers, or completely ignoring return types, the vue drop handler will now simply set `app.dragOverNode` and allow the `document` drop handler to resolve node drag/drop operations without any of the difficulty from propagation. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11691-Short-circuit-asset-reuploads-simplify-node-dnd-34f6d73d36508157af86e6cf09229781) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
bb74ec94de |
test(load3d): add unit tests for 9 previously-untested controls (#11730)
## Summary Mirror the maskeditor coverage approach for load3d sub-components. Each component gets behavior tests covering rendering, conditional branches, v-model bidirectional sync, and emitted events. - ViewerLightControls: setting-store min/max/step + v-model - ViewerExportControls: format dropdown + click-to-export - ViewerCameraControls: type select, FOV slider visibility - ViewerSceneControls: with/without bg image branches, render mode - PopupSlider: trigger toggle, click-outside dismissal, defaults - CameraControls: switch button, FOV PopupSlider visibility - ExportControls: trigger popup, format selection, click-outside - AnimationControls: empty-list bypass, controls, time formatting - ViewerControls: dialog open routing + onClose wiring ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11730-test-load3d-add-unit-tests-for-9-previously-untested-controls-3506d73d365081eaa9e7c5d0b922fc14) by [Unito](https://www.unito.io) |
||
|
|
e7640d414b |
test: add E2E tests for ActionBarButtons toolbar component (#11561)
## Summary
- Add E2E tests for the `ActionBarButtons` toolbar component (FE-111)
- Add `data-testid="action-bar-buttons"` to the container div for stable
test targeting
- Register `TestIds.topbar.actionBarButtons` in `selectors.ts`
## Changes
- `browser_tests/tests/actionBarButtons.spec.ts` — 6 tests across 5
scenarios: empty state, button rendering, icon rendering, multiple
buttons, click handler, mobile label hiding
- `src/components/topbar/ActionBarButtons.vue` — adds `data-testid` to
container
- `browser_tests/fixtures/selectors.ts` — registers new test ID
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Primarily adds Playwright coverage and a `data-testid` attribute;
runtime behavior is unchanged aside from an extra DOM attribute.
>
> **Overview**
> Adds a new Playwright spec (`actionBarButtons.spec.ts`) that verifies
the ActionBarButtons container empty state, rendering (label/icon),
multiple buttons, click handler execution, and mobile label-hiding
behavior by registering buttons via `window.app!.registerExtension`.
>
> Updates the UI and test selector plumbing by adding
`data-testid="action-bar-buttons"` to `ActionBarButtons.vue` and
exposing it as `TestIds.topbar.actionBarButtons` for stable E2E
targeting.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
517da289f6 |
feat: Search - add ghost node following setting and increase opacity (#11365)
## Summary Adds setting to disable the node auto-follow cursor behavior when adding nodes from the search, and increased the visibilty of Vue ghost nodes. ## Changes - **What**: - add setting - increase opacity - add test ## 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) Before <img width="452" height="517" alt="image" src="https://github.com/user-attachments/assets/369c0d90-5352-482b-a1b3-36180bffb3ee" /> After <img width="440" height="536" alt="image" src="https://github.com/user-attachments/assets/2066fdd4-6eb4-4bfb-ac7c-559fc99de57d" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11365-feat-Search-add-ghost-node-following-setting-and-increase-opacity-3466d73d3650811b9c27ed4cc930816d) by [Unito](https://www.unito.io) |
||
|
|
fc2a4e82cf |
feat(load3d): bind UI capability gating to ModelAdapterCapabilities (#11711)
> Final piece of the PLY / 3D Gaussian Splatting series. Previous PR made `ModelAdapterCapabilities` load-bearing on the engine side; the UI was still gating off `isSplatModel` / `isPlyModel` proxies. This PR routes the viewer and the viewer-mode dialog through the capability fields directly, so the same source of truth that drives `Load3d` behavior also drives what the user sees. Eighth and last in the series splitting up. ## Summary Snapshot `Load3d.getCurrentModelCapabilities()` into 5 Vue refs on each model load and pipe them through the existing `Load3D` / `Load3DControls` / `Load3dViewerContent` / `ModelControls` / `ViewerModelControls` / `Preview3d` props. Replaces the format-specific `:is-splat-model` / `:is-ply-model` props and the hardcoded "splat → drop light/gizmo/export" subtraction with additive capability gates. No engine behavior changes — capability values are what previous PR already produces; UI now consumes them. ## Changes - **`useLoad3d.ts` / `useLoad3dViewer.ts`**: 5 new refs (`canFitToViewer` / `canUseGizmo` / `canUseLighting` / `canExport` / `materialModes`) refreshed on every load via `load3d.getCurrentModelCapabilities()`. `useLoad3dViewer` extracts the snapshot into a single `captureAdapterFlags(source)` helper because it runs in three places (initializeViewer / initializeStandaloneViewer / loadStandaloneModel). - **`Load3D.vue`**: gate the fit-to-viewer button on `canFitToViewer`; pass capability refs to `Load3DControls` instead of `isSplatModel` / `isPlyModel`. - **`Load3DControls.vue`**: build `availableCategories` additively (`['scene','model','camera']` plus `light` / `gizmo` / `export` if their capability is true) rather than subtracting from a fixed list when `isSplatModel` is true. Forwards `materialModes` to `ModelControls`. - **`Load3dViewerContent.vue`**: gate the light / gizmo / export sidebar sections on the capability refs; pass `materialModes` to `ViewerModelControls`. - **`ModelControls.vue` / `ViewerModelControls.vue`**: drop the local `materialModes` computed (which derived its options from `isPlyModel` and a hardcoded mesh list) and accept `materialModes` as a `readonly MaterialMode[]` prop. An empty array hides the dropdown entirely. - **`Preview3d.vue`** (renderer linearMode): mirror the prop swap on the standalone preview path. ## Review Focus - **Capability prop wiring is the only public-API change for child components**. `ModelControls` and `ViewerModelControls` lost `hideMaterialMode` / `isPlyModel` props. Any extension that imported these components directly will need to migrate, but they're internal `src/components/load3d/controls/**` files and not part of the documented extension surface. - **Empty-`materialModes` semantics**: previously hidden via `:hide-material-mode`; now hidden via `materialModes.length === 0`. `SplatModelAdapter` declares `materialModes: []`, so the splat case keeps the same behavior — the dropdown disappears. PLY adds `'pointCloud'` to the array, so the dropdown picks up that mode automatically without the controls needing an `isPlyModel` branch. - **`captureAdapterFlags` runs after every load completes**, so switching between mesh and splat in the same viewer instance updates the chrome correctly. Verified via the new `Load3D.test.ts` / `Load3dViewerContent.test.ts` cases. - **Capability gating is inclusive of `canFitToViewer`** in this PR even though `Load3DControls` has no fit category — the fit-to-viewer floating button on `Load3D.vue` is what reads it. PLY's `fitToViewer: true` means the button stays visible for PLY users. ## Coverage | File | Stmts | Branch | Funcs | |---|---|---|---| | `Load3D.vue` (modified) | 53.3% | **95.5%** | 83.3% | | `Load3DControls.vue` (modified) | 77.5% | **94.8%** | 86.4% | | `Load3dViewerContent.vue` (modified) | 60.6% | 72.1% | 54.5% | | `controls/ModelControls.vue` (modified) | 16.3% | 0% | 0% | | `controls/viewer/ViewerModelControls.vue` (modified) | **100%** | **100%** | **100%** | | `composables/useLoad3d.ts` (modified) | 78.7% | 64.5% | 71.4% | | `composables/useLoad3dViewer.ts` (modified) | 76.0% | 52.1% | 66.7% | Four new test files (`Load3D.test.ts` / `Load3DControls.test.ts` / `Load3dViewerContent.test.ts` / `controls/viewer/ViewerModelControls.test.ts`) cover the new capability gating directly: each component is rendered with capability flags toggled on/off and the appropriate sidebar / dropdown / button visibility is asserted. Capability prop forwarding from `Load3D.vue` → `Load3DControls.vue` and from `Load3dViewerContent.vue` → `ViewerModelControls.vue` is exercised end-to-end. `controls/ModelControls.vue` is the legacy node-side ModelControls — its existing tests live elsewhere and were not in this PR's scope; the diff line covered (the `v-if="materialModes.length > 0"` swap) is exercised by the new `Load3DControls.test.ts` cases that drive a non-empty / empty `materialModes` through. `Preview3d.vue` (renderer linearMode) has no test file in the project; the prop swap there is the same shape as the `Load3D.vue` swap which is covered. `useLoad3d.ts` / `useLoad3dViewer.ts` percentages are roughly the pre-existing baseline. The diff lines (the 5 new refs and the `captureAdapterFlags` helper) are exercised by the existing composable tests via the mock that now stubs `getCurrentModelCapabilities()`. 73 new component unit tests; 393 total load3d-related tests pass on this branch. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11711-feat-load3d-bind-UI-capability-gating-to-ModelAdapterCapabilities-3506d73d365081b3af68f30e3f728e24) by [Unito](https://www.unito.io) |
||
|
|
bc11b5ff5e |
test: add E2E tests for LoginButton toolbar component (#11562)
## Summary
- Add E2E tests for the `LoginButton` toolbar component (FE-109)
- Add `data-testid="login-button"` to `LoginButton.vue` for stable
targeting
- Register `TestIds.topbar.loginButton` in `selectors.ts`
## Changes
- `browser_tests/tests/loginButton.spec.ts` — 7 tests covering:
visibility toggled by `show_signin_button` server feature flag, ARIA
label, click → sign-in dialog, hover popover content, popover dismissal
- `src/components/topbar/LoginButton.vue` — adds `data-testid`
- `browser_tests/fixtures/selectors.ts` — registers new test ID
## Notes
`LoginButton` is rendered in `WorkflowTabs` when `flags.showSignInButton
?? isDesktop` is true. Since `isDesktop = false` in the OSS test build
(`DISTRIBUTION=localhost`), tests enable the button by setting
`window.app.api.serverFeatureFlags.value.show_signin_button = true` —
the established pattern used throughout the test suite (e.g.
`nodeLibraryEssentials.spec.ts`, `shareWorkflowDialog.spec.ts`).
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to adding stable `data-testid` hooks
plus new Playwright E2E coverage, with only a minor adjustment to a
unit-test performance threshold.
>
> **Overview**
> Adds Playwright E2E coverage for the topbar `LoginButton`, including
feature-flag-driven visibility, ARIA labeling, click-to-open sign-in
dialog, and hover popover behavior (including the *Learn more* link and
dismissal).
>
> To make the tests stable, `LoginButton.vue` now exposes `data-testid`
attributes for the button and popover elements, and `selectors.ts`
registers new `TestIds.topbar.*` entries.
>
> Relaxes the `useModelToNodeStore.getCategoryForNodeType` performance
test threshold (from 10ms to 100ms) while clarifying the intent of the
check.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
52e73f2697 |
test: Expand node search box V2 e2e coverage (#10620)
## Summary Adds additional browser test coverage to the v2 node search ## Changes - **What**: - extend search v2 fixtures - add additional tests - add data-testid for targeting elements - rework tests to work with new menu UI ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10620-test-Expand-node-search-box-V2-e2e-coverage-3306d73d365081b6bad3f73daab1194f) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
b4d209b5f6 |
feat: refresh missing models through pipeline (#11661)
## Summary Follow-up to the closed earlier attempt in #11646. This PR keeps the same user-facing goal, but changes the implementation to reuse the existing missing model pipeline for refresh instead of maintaining a separate candidate-only recheck path. Adds a missing model refresh action in the Errors tab by reusing the existing missing model pipeline, so users can re-check models after downloading or manually placing files without reloading the workflow. ## Changes - **What**: - Adds `app.refreshMissingModels()` as a reusable refresh entry point for the current root graph. - Splits node definition reloading into `app.reloadNodeDefs()` so missing-model refresh can pull fresh `object_info` without showing the generic combo refresh success flow. - Reuses the existing missing model pipeline instead of adding a separate candidate-only checker. The refresh path serializes the current graph, reuses active workflow model metadata when available, falls back to current missing-model metadata, and then reruns the same candidate discovery/enrichment/surfacing flow used during workflow load. - Adds missing model refresh state and error handling to `missingModelStore`. - Adds a Refresh button next to Download all in the missing model card action bar. - Moves Download all from the Errors tab header into the missing model card, so the Download all and Refresh actions render or hide together. - Changes Download all visibility from “more than one downloadable model” to “at least one downloadable model.” - Keeps the action bar hidden when there are no downloadable missing models; Cloud still does not render this action area. - Normalizes active workflow `pendingWarnings` updates so resolved missing model warnings do not get revived by stale empty warning objects. - Adds test IDs and coverage for the new action bar, refresh state, refresh delegation, pending warning sync, and E2E refresh behavior. - **Breaking**: None. - **Dependencies**: None. ## Review Focus The main design choice is intentionally reusing the missing model pipeline for refresh instead of implementing a smaller candidate-only recheck. The earlier candidate-only approach was cheaper, but it created a separate source of truth for missing-model resolution and made edge cases harder to reason about. In particular, it could diverge from the behavior used when a workflow is loaded, and it did not naturally handle the case where a model becomes missing after the workflow is already open. This version pays the cost of refreshing node definitions and rerunning the missing-model scan for the current graph, but keeps the refresh behavior aligned with workflow load semantics. Expected behavior by environment: - OSS browser: - The action bar appears when at least one missing model has a downloadable URL and directory. - Download all uses the existing browser download path. - Refresh reloads `object_info`, refreshes node definitions/combo values, reruns missing-model detection for the current graph, and clears the error if the selected model is now available. - OSS desktop: - The same action bar appears under the same downloadable-model condition. - Download all uses the existing Electron DownloadManager path. - Refresh uses the same missing-model pipeline as browser, so manually placed files or desktop-downloaded files can be rechecked without reloading the workflow. - Cloud: - The action bar remains hidden because model download/import is not supported in this section for Cloud. A few boundaries are intentional: - This PR does not add automatic filesystem watching. Browser OSS cannot reliably observe local model folder changes, so the user-triggered Refresh button remains the cross-environment mechanism. - This PR does not redesign the public `refreshComboInNodes` API beyond extracting `reloadNodeDefs()` for reuse. Further cleanup of toast behavior or a more explicit object-info reload API can be follow-up work. - This PR keeps refresh scoped to missing-model validation; missing media and missing nodes continue to use their existing flows. Linear: FE-417 ## Screenshots (if applicable) https://github.com/user-attachments/assets/2e02799f-1374-4377-b7b3-172241517772 ## Validation - `pnpm format` - `pnpm lint` (passes; existing unrelated warning remains in `src/platform/workspace/composables/useWorkspaceBilling.test.ts`) - `pnpm typecheck` - `pnpm test:unit` - `pnpm test:browser:local -- --project=chromium browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts` - `pnpm build` - `NX_SKIP_NX_CACHE=true DISTRIBUTION=desktop USE_PROD_CONFIG=true NODE_OPTIONS='--max-old-space-size=8192' pnpm exec nx build` - Manual desktop verification through `~/Projects/desktop` after copying the desktop build into `assets/ComfyUI/web_custom_versions/desktop_app`: - confirmed the FE bundle is built with `DISTRIBUTION = "desktop"` - confirmed missing model Download uses the desktop download path instead of browser download - confirmed Refresh can clear the missing model error after the model is available - Push hook: `pnpm knip --cache` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11661-feat-refresh-missing-models-through-pipeline-34f6d73d3650811488defee54a7a6667) by [Unito](https://www.unito.io) |
||
|
|
b2bba78ce0 |
test: add unit tests for usePanAndZoom composable (#11391)
## Summary Add 29 unit tests for the `usePanAndZoom` composable to improve mask editor test coverage. ## Changes - **What**: New test file `src/composables/maskeditor/usePanAndZoom.test.ts` covering all public API methods ## Review Focus Test coverage spans: - `initializeCanvasPanZoom` — landscape/portrait fit-to-view, panel width accounting, style application - `handlePanStart`/`handlePanMove` — panning state, offset updates, error on missing start - `zoom` — wheel in/out, clamping (0.2–10.0), missing canvas early return, cursor update - `updateCursorPosition` — pan offset application - `invalidatePanZoom` — missing image warning, store container fallback, rgbCanvas dimension sync - Touch handlers — single/two-finger start, double-tap undo, pen blocking, single-touch pan, pinch zoom, touch end states - `addPenPointerId`/`removePenPointerId` — add, dedup, remove, no-op ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11391-test-add-unit-tests-for-usePanAndZoom-composable-3476d73d36508104b447d87471ce021b) by [Unito](https://www.unito.io) --------- Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
3340b77908 |
test: add unit tests for value-control widget family (#11440)
## Summary Adds 42 unit tests across 5 files covering the value-control widget family — first batch of a broader effort to raise widget-test coverage. ## Changes - **What**: - `WidgetInputNumber.test.ts` (9) — variant selection by widget.type (int/float/slider/gradientslider), controlWidget wrapping in WidgetWithControl, modelValue forwarding. - `WidgetInputNumberGradientSlider.test.ts` (11) — initial value, min/max/disabled pass-through, default vs custom gradient stops, precision-derived step, WidgetLayoutField wrapping. - `WidgetWithControl.test.ts` (5) — renders passed component with widget/modelValue, initializes ValueControlButton mode from widget.controlWidget.value, calls controlWidget.update on mode change. - `ValueControlButton.test.ts` (11) — i18n aria-label per mode, text vs icon rendering, pointer and keyboard activation, `type="button"` safety. - `ValueControlPopover.test.ts` (6) — BEFORE/AFTER copy from settingStore, four option render, v-model updates on selection. ## Review Focus - Stack follows the existing widget-test pattern (`@testing-library/vue` + PrimeVue + `createI18n` where needed, no `@vue/test-utils`). - `createMockWidget` from `widgetTestUtils.ts` reused; no new helper extracted (YAGNI — per-file `renderComponent` stays ~10 lines). - `WidgetWithControl` watcher test asserts first-arg of `update` since the vue watch callback passes `(newVal, oldVal, onCleanup)`. - No changes to any widget component source — tests-only PR. This is one of several focused PRs in a widget-test-coverage sequence; subsequent PRs cover form-dropdown internals, utility widgets, media/graph/canvas widgets, and e2e value-type specs. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11440-test-add-unit-tests-for-value-control-widget-family-3486d73d3650813891e1fe8d45eaecaf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: bymyself <cbyrne@comfy.org> |
||
|
|
4c892341e4 |
feat: Node search UX updates (#9714)
## Summary Addresses feedback from the initial v2 node search implementation for improved UI and UX ## Changes - **What**: - add root filter buttons - remove all extra tree categories leaving only "Most relevant" - replace input/output selection with popover - replace price badge with one from node header - add chevrons and additional styling to category tree - hide empty categories - fix bug with hovering selecting item under mouse automatically - fix tailwind merge with custom sizes removing them - keyboard navigation - general tidy/refactor/test ## Screenshots (if applicable) https://github.com/user-attachments/assets/db798dfa-e248-4b48-bb56-2fa7b6c5f65f ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9714-feat-Node-search-UX-updates-31f6d73d365081cebd96c4253ad1ca53) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
0f66f76b87 |
test: add unit tests for mask editor control components (#11642)
## Summary
Add unit tests for the three mask editor control components
(`DropdownControl`, `SliderControl`, `ToggleControl`), raising each from
partial (20% / 37.5% / 44.4%) to 100% across all coverage dimensions.
## Changes
- **What**: Add three sibling test files under
`src/components/maskeditor/controls/`:
- `DropdownControl.test.ts` (5 tests): label render, normalization of
`string[]` options into `{label,value}`, pass-through of `{label,value}`
options, `modelValue` reflected as the selected option,
`update:modelValue` emitted on change.
- `SliderControl.test.ts` (4 tests): label render,
`min`/`max`/`step`/`modelValue` exposed on `<input type="range">`,
`step` defaults to `1` when omitted, `update:modelValue` emitted as a
`number` (not string) on input.
- `ToggleControl.test.ts` (5 tests): label render, `modelValue`
reflected as `checked`, `update:modelValue` emitted as `true` / `false`
on toggle.
## Review Focus
- Stack matches the project's prevailing Vue test pattern
(`@testing-library/vue` + `@testing-library/user-event`, e.g.
`Badge.test.ts`, `BatchCountEdit.test.ts`).
- `update:modelValue` is asserted via the `'onUpdate:modelValue'`
callback prop rather than `emitted()` — keeps tests focused on
observable behavior and avoids reaching into the wrapper.
- `SliderControl` uses `fireEvent.input` instead of
`userEvent.type/clear`: `<input type="range">` is non-editable for
`userEvent` (`clear() is only supported on editable elements`). The
single eslint-disable for `testing-library/prefer-user-event` is
annotated with the reason.
- `DropdownControl` test guards both branches of the `string` →
`{label,value}` normalization (string array path and pre-normalized
object array path), since that's the only meaningful logic in the file.
- Style aligned with sibling tests: `should ...` naming, per-file
`renderComponent` helper accepting a `props` override and an `onUpdate`
callback parameter.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11642-test-add-unit-tests-for-mask-editor-control-components-34e6d73d3650812aba2ae34a760489e2)
by [Unito](https://www.unito.io)
|
||
|
|
7bfbd0d7f3 |
test: add unit tests for mask editor settings panels (#11647)
## Summary
Add unit tests for the five mask editor settings panel components,
raising each from 0–35% to ~98–100% across coverage dimensions.
## Changes
- **What**: Add 5 sibling test files under `src/components/maskeditor/`
(47 tests total):
- `PaintBucketSettingsPanel.test.ts` (4): both sliders bind to / write
through to `setPaintBucketTolerance` / `setFillOpacity`. **100%**.
- `ColorSelectSettingsPanel.test.ts` (8): all 7 controls (4 sliders, 2
toggles, 1 dropdown) bind to and write through to the right store
fields/setters; method dropdown casts to `ColorComparisonMethod`.
**100%**.
- `BrushSettingsPanel.test.ts` (13): shape buttons (Arc/Rect), reset to
default, four numeric inputs (size/opacity/hardness/step), logarithmic
size slider including the cached-raw-value path
(`Math.round(Math.pow(250, x))`), color input v-model, color input ref
forwarded to / cleared from store on mount/unmount.
- `ImageLayerSettingsPanel.test.ts` (17): mask opacity slider + canvas
style sync, blend-mode select with all 3 enum values + default fallback,
three layer-visibility checkboxes (with and without canvas refs),
activate-layer button forwarding to `toolManager.setActiveLayer`,
disabled-when-active and "show paint button only when Eraser" branches,
base image preview src binding.
- `SettingsPanelContainer.test.ts` (5): tool → component routing
(`MaskBucket` → bucket panel, `MaskColorFill` → color panel, anything
else → brush panel).
## Review Focus
- Stack matches sibling control tests (`@testing-library/vue` +
`userEvent` + stub child controls). Each panel's child `SliderControl` /
`ToggleControl` / `DropdownControl` is replaced by a minimal `<button>`
stub that emits `update:modelValue` on click, so panel logic is
decoupled from control internals (already covered by their own tests).
- Two panels (`Brush`, `ImageLayer`) need real reactivity
(`brushSettings.size` changes through a setter must propagate to the
slider's modelValue computed; `currentTool` / `activeLayer` mutations
between tests). They use `reactive()` from Vue at top level + a `let
mockStore` reset in `beforeEach`. Plain object mocks would either
silently no-op or leak state between tests.
- The two reactive panels enable file-level `eslint-disable
testing-library/no-container, testing-library/no-node-access` because
the unlabeled DOM (shape divs, unlabeled `<input type="number">`/`<input
type="color">`/`<input type="checkbox">`/`<select>`) genuinely can't be
queried via `screen.getByRole/Label`. Each disable has a comment
explaining why.
- `BrushSettingsPanel` has a non-trivial cached-value branch in
`brushSizeSliderValue` computed: when `rawSliderValue` and `brushSize`
are in sync, the getter returns the raw float instead of recomputing
`log(size)/log(250)`. Test stubs `setBrushSize` to actually write back
so the next read takes the cached path.
- `ImageLayerSettingsPanel` has a `display: 'block' | 'none'` style
binding. happy-dom doesn't populate `el.style.display` from inline style
strings reliably, so the test asserts via `el.getAttribute('style')`
instead.
- `SettingsPanelContainer` uses inline component stubs that render
unique text — assertion is just `textContent.toContain('brush-panel')`
etc. No need for component-instance probing.
- Style aligned with sibling tests: `should ...` naming, `describe`
grouped by feature (button group, slider, etc.), `vi.hoisted` for mock
state where reactivity isn't required.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11647-test-add-unit-tests-for-mask-editor-settings-panels-34e6d73d36508117911bc0850ce085e1)
by [Unito](https://www.unito.io)
|
||
|
|
32b266f3e9 |
test: add unit tests for SidePanel, MaskEditorButton, and BrushCursor (#11648)
## Summary
Add unit tests for three small mask editor Vue components (`SidePanel`,
`MaskEditorButton`, `BrushCursor`), all reaching **100%** across
coverage dimensions.
## Changes
- **What**: Add 3 sibling test files (17 tests total):
- `SidePanel.test.ts` (3): renders both `SettingsPanelContainer` and
`ImageLayerSettingsPanel`; forwards `toolManager` prop through to
`ImageLayerSettingsPanel`; works with the prop omitted.
- `MaskEditorButton.test.ts` (3): renders with localized `aria-label`
when `isSingleImageNode` is true; hidden via `v-show` (style `display:
none`) when false; click executes `Comfy.MaskEditor.OpenMaskEditor`
command.
- `BrushCursor.test.ts` (11): opacity 1 / 0 from `brushVisible`; size =
2 × effective brush size × zoom (with hardness scaling); border-radius
50% / 0% for Arc / Rect; position from `cursorPoint + panOffset -
radius`, with optional `containerRef.getBoundingClientRect` offset;
gradient preview hidden / shown; flat fill at `effectiveHardness === 1`.
## Review Focus
- `SidePanel` test stubs both child panels to bare `<div data-testid>`
so the test verifies wiring (prop forwarding, child rendering) without
being affected by the children's i18n / store dependencies.
- `MaskEditorButton` mocks `useCommandStore.execute` and
`useSelectionState.isSingleImageNode` (as a Vue ref). Real i18n via
`createI18n` + `globalInjection: true` — the template uses `$t(...)`
which requires global injection in composition mode.
- For the v-show hidden case, `getByLabelText('...', { selector:
'button' })` works where `getByRole('button', { hidden: true })` doesn't
reliably resolve through the `<Button>` wrapper component.
- `BrushCursor` uses `reactive()` mock store and queries the brush /
gradient elements by id (`#maskEditor_brush`,
`#maskEditor_brushPreviewGradient`). File-level eslint-disable for
`testing-library/no-node-access` because the component's anchor elements
are styled divs without ARIA roles or labels.
- The `radial-gradient` (hardness < 1) branch of `gradientBackground` is
intentionally **not** asserted via rendered DOM: the computed returns a
multi-line template literal that happy-dom's CSS parser drops entirely.
The math (`getEffectiveBrushSize` / `getEffectiveHardness`) is covered
by `brushUtils.test.ts`. v8 reports 100% branch coverage because Vue
evaluates the computed regardless of whether the resulting style string
is parsed by the DOM.
- Style aligned with sibling tests: `should ...` naming, `describe`
grouped by feature, `vi.hoisted` for command-store / selection-state
mocks, real i18n via `createI18n`.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11648-test-add-unit-tests-for-SidePanel-MaskEditorButton-and-BrushCursor-34e6d73d365081e38c4afee32ddf2b0b)
by [Unito](https://www.unito.io)
|
||
|
|
2d4ca9c387 |
test: add unit tests for PointerZone and ToolPanel (#11649)
## Summary
Add unit tests for `PointerZone` and `ToolPanel` mask editor components,
raising each from 0% to **91.89% / 100%** respectively.
## Changes
- **What**: Add 2 sibling test files (24 tests total):
- `PointerZone.test.ts` (13): mount exposes root element to
`store.pointerZone`; pointer events (down/move/up) forward to
`toolManager.handlePointer*`; `pointerleave` hides brush + clears
cursor; `pointerenter` calls `toolManager.updateCursor`; touch events
(start/move/end) forward to `panZoom.handleTouch*`; wheel zooms then
updates cursor with wheel coords; `isPanning` watcher sets cursor to
"grabbing" then back via `updateCursor`; contextmenu's default is
prevented.
- `ToolPanel.test.ts` (11): one container rendered per `allTools`; icon
HTML for each tool; current tool highlighted with
`maskEditor_toolPanelContainerSelected` class while others are not;
clicking a container calls `toolManager.switchTool` with the matching
enum value; zoom indicator rounds `displayZoomRatio * 100`; dimensions
text reflects `image.width × image.height` (or empty when no image);
clicking the zoom indicator calls `store.resetZoom`.
## Review Focus
- `PointerZone` mocks `useToolManager` / `usePanAndZoom` to plain
function bags via factory helpers. The component is mostly forwarding,
so the test's value is in *event mapping* (which event triggers which
handler), not handler internals.
- happy-dom doesn't propagate `clientX` / `clientY` through the
`WheelEvent` constructor; the wheel test sets them via
`Object.defineProperty` after construction. Without this,
`updateCursorPosition` reads `undefined`.
- `PointerZone` ends at 91.89% statements / 70% branch — uncovered lines
are the `onMounted` early-return when `pointerZoneRef.value` is
`undefined` (always set in tests) and the watcher's same guard. Both
unreachable in normal use, intentionally not covered.
- `ToolPanel` mocks `iconsHtml` to `<svg data-testid="icon-${tool}" />`
markers, letting tests assert per-tool rendering via `getByTestId`. Real
i18n via `createI18n` for the reset-zoom tooltip text.
- `getToolContainers` queries by class because tool buttons are
unlabeled `<div>`s with click handlers (no role / aria); a file-level
`eslint-disable testing-library/no-node-access` covers this and the
dimensions-span placeholder query.
- Style aligned with sibling tests: `should ...` naming, `describe`
grouped by feature, `reactive()` mock store + `let mockStore` reset in
`beforeEach`, real i18n where keys are user-visible.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11649-test-add-unit-tests-for-PointerZone-and-ToolPanel-34e6d73d3650817daf85c0d33d16899d)
by [Unito](https://www.unito.io)
|
||
|
|
6f6fc88b0f |
test: add unit tests for MaskEditorContent main container (#11651)
## Summary
Add unit tests for `MaskEditorContent` (the mask editor's main
orchestration container), raising coverage from 0% to **94.11% / 83.72%
/ 83.33% / 94.11%** (statements / branches / functions / lines).
## Changes
- **What**: Add `src/components/maskeditor/MaskEditorContent.test.ts`
(12 tests) covering:
- **Mount**: keyboard listeners attached, ResizeObserver observes the
container, all 5 canvas refs assigned to the store before init runs.
- **Init flow**: `loader.loadFromNode` → `imageLoader.loadImages` →
`panZoom.initializeCanvasPanZoom` → `canvasHistory.saveInitialState` →
`brushDrawing.initGPUResources` → `initPreviewCanvas` chain runs in
order; child UI (`ToolPanel` / `PointerZone` / `SidePanel` /
`BrushCursor`) only renders after init succeeds; GPU preview canvas
resolution matches the mask canvas.
- **Init errors**: rejection from `loader.loadFromNode` or
`panZoom.initializeCanvasPanZoom` is caught, logged, and triggers
`dialogStore.closeDialog()`.
- **ResizeObserver**: callback invokes `panZoom.invalidatePanZoom()`
(captured the constructor argument to call it manually).
- **Drag**: `Ctrl+drag` is preventDefault'd; plain drag is not.
- **Unmount**: cleanup runs `brushDrawing.saveBrushSettings`,
`keyboard.removeListeners`, `canvasHistory.clearStates`,
`store.resetState`, `dataStore.reset`.
## Review Focus
- Heavy mock surface (10 modules): the 3 stores, 5 composables, plus 4
child Vue components and `LoadingOverlay`. All mocks are `vi.hoisted`
module-level. `mockStore` is `reactive()` because the source mutates
`activeLayer` (visible in template binding), `maskCanvas`, etc.; the
rest are plain function bags.
- Child components are stubbed to bare `<div data-testid>` so init
reveal can be asserted via `screen.findByTestId(...)` without engaging
their real implementations (each has its own test file).
- `MockResizeObserver` captures the constructor callback in module-level
`lastResizeCallback`. The "invalidate on resize" test invokes it
manually with empty args — that's enough to exercise the source's `if
(panZoom) { await panZoom.invalidatePanZoom() }` branch since the
callback only consumes `panZoom` from closure.
- happy-dom doesn't propagate `ctrlKey` through the `DragEvent`
constructor, so the drag tests set it via `Object.defineProperty(event,
'ctrlKey', { value })` (same pattern used in `PointerZone.test.ts` for
wheel `clientX/Y`).
- 94.11% line coverage — the two uncovered blocks (`containerRef`
missing, canvas refs missing) are early-return error paths unreachable
when Vue successfully mounts; not worth constructing a fixture to
trigger.
- Style aligned with sibling tests: `should ...` naming, `describe`
grouped by feature, `vi.hoisted` mocks reset via `beforeEach`,
`screen.findByTestId` for async render assertions.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11651-test-add-unit-tests-for-MaskEditorContent-main-container-34e6d73d365081b38af2e057cb7daf9e)
by [Unito](https://www.unito.io)
|
||
|
|
492bec28c8 |
test: add unit tests for TopBarHeader (#11650)
## Summary
Add unit tests for `TopBarHeader` mask editor dialog component, raising
coverage from 0% to **100%** across statements, branches, functions, and
lines.
## Changes
- **What**: Add `src/components/maskeditor/dialog/TopBarHeader.test.ts`
(17 tests) covering:
- Localized title rendering.
- Undo / Redo buttons forward to `store.canvasHistory.{undo,redo}`.
- Four transform buttons (rotate left / right, mirror horizontal /
vertical) call the matching `canvasTransform` action — parametrized via
`it.each`.
- All four transform error paths: rejected promise is caught, swallowed,
and logged with the right `[TopBarHeader] ... failed:` prefix.
- Invert calls `canvasTools.invertMask`; Clear calls both
`canvasTools.clearMask` and `store.triggerClear`.
- Save: hides brush, awaits `saver.save()`, closes the dialog on
success; switches button text to "Saving" while in-flight; restores
brush + button label and logs on save failure.
- Cancel: closes the dialog with the `global-mask-editor` key.
## Review Focus
- All five composable / store dependencies are mocked at module level
via `vi.hoisted`: `useMaskEditorStore`, `useDialogStore`,
`useCanvasTools`, `useCanvasTransform`, `useMaskEditorSaver`. Only the
store needs `reactive()` (`brushVisible` flips during save flow); the
rest are plain function bags.
- `Button.vue` is stubbed to a thin `<button :disabled>` so role queries
(`getByRole('button', { name: ... })`) resolve cleanly without dragging
in the real UI button's classes / variants.
- Real i18n via `createI18n`. Icon buttons rely on `:title` for their
accessible name; text buttons rely on slot text — both are reachable
through `getByRole('button', { name: ... })`.
- The "Saving" text test uses an unresolved promise to keep the
in-flight state observable; `waitFor` + `void user.click(...)` lets us
assert without awaiting the click. The dangling promise resolves at the
end so vitest doesn't complain.
- `it.each` parametrizes the four transform success paths and
(separately) the four error paths, keeping the file tight.
- Style aligned with sibling tests: `should ...` naming, `describe`
grouped by feature, `vi.hoisted` for cross-test mocks, real i18n with
`createI18n`.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11650-test-add-unit-tests-for-TopBarHeader-34e6d73d365081adab66e460cf56accb)
by [Unito](https://www.unito.io)
|
||
|
|
ba6dd2a09c |
refactor(load3d): extract viewport math to load3dViewport (#11624)
## Summary Pull two pure helpers out of `Load3d` into a sibling module. Mechanical refactor — no behavior change. Second of four small PRs splitting up the https://github.com/Comfy-Org/ComfyUI_frontend/pull/11495. ## Changes - **What**: New `load3dViewport.ts` exports two pure functions: - `computeLetterboxedViewport({ width, height }, targetAspectRatio)` returns `{ offsetX, offsetY, width, height }` — the aspect-ratio fitting math that was duplicated inline in three places (`renderMainScene`, `setBackgroundImage`, `handleResize`). - `isLoad3dActive(flags)` consumes the activity-flag struct used by the rAF tick gate; `Load3d.isActive()` now delegates to it. ## Review Focus - The three call sites in `Load3d.ts` produce byte-identical viewport rectangles for any `(containerWidth, containerHeight, targetAspect)` triple — same branch on aspect-ratio comparison, same offset derivation. Simplest way to verify: diff the math. - `isLoad3dActive` ORs the same six flags as before in the same order. Old implementation read `this.STATUS_MOUSE_ON_NODE` etc. directly; new one reads them through a flags object built at the call site. - 13 unit tests cover the helpers: the "wider" / "taller" / "matching" aspect cases for letterboxing, and each individual flag flipping `isLoad3dActive` on by itself. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11624-refactor-load3d-extract-viewport-math-to-load3dViewport-34d6d73d365081ab9af5fc445bf5bd5a) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
4a9001f675 |
test: add E2E tests for image crop widget Levels 4-7 (#11571)
## Summary
Adds 12 Playwright E2E tests for the `ImageCropV2` widget covering
aspect ratio selection, lock/unlock behavior, constrained resize, and
BoundingBox numeric input — all of which had zero test coverage.
## Changes
**Level 4 — Aspect Ratio Selection** (`with source image after
execution`)
- Selecting 16:9 preset adjusts crop height proportionally via
`applyLockedRatio`
- Selecting Custom unlocks the ratio and restores all 8 resize handles
**Level 5 — Lock/Unlock** (`without source image` + `with source image
after execution`)
- Selecting a preset auto-enables the lock (aria-label changes to
"Unlock aspect ratio")
- Unlocking after a preset reverts the dropdown display to "Custom"
- Full lock→unlock round-trip verifies handle count (4 → 8) and
aria-label on both transitions
**Level 6 — Constrained Resize** (`with source image after execution`)
- NW corner drag grows origin (x, y decrease) and dimensions while
maintaining ratio
- SE corner drag beyond image edge clamps to boundary
- NW corner drag beyond (0, 0) clamps x/y to image boundary
- Inward SE corner drag enforces `MIN_CROP_SIZE` (16px minimum)
**Level 7 — BoundingBox Numeric Input** (`with source image after
execution`)
- X increment button increments crop x by 1
- Width increment button increments crop width by 1
- BoundingBox inputs reflect updated position after a drag
No source code was modified.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to Playwright E2E coverage plus adding
`data-testid` attributes to BoundingBox inputs, with no behavioral logic
changes expected.
>
> **Overview**
> **Expands E2E coverage for the `ImageCropV2` widget** by adding new
Playwright tests for ratio preset selection, lock/unlock behavior,
constrained resizing (including boundary clamping and min size), and
BoundingBox numeric input updates.
>
> **Improves testability of BoundingBox controls** by adding
`data-testid` attributes to the `WidgetBoundingBox.vue`
`ScrubableNumberInput` fields (`x`, `y`, `width`, `height`) so E2E tests
can target increment/input elements reliably.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
9cf035879f |
test: add WidgetCurve unit tests (#11469)
## Summary Splits the WidgetCurve test coverage out of #11446 so this widget can be reviewed independently. ## Changes - **What**: Adds WidgetCurve unit tests covering point forwarding, interpolation updates, disabled-state behavior, and upstream value handling. ## Review Focus Focused test-only PR extracted from #11446. Validated with `pnpm test:unit -- --run src/components/curve/WidgetCurve.test.ts`. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11469-test-add-WidgetCurve-unit-tests-3486d73d365081c2a68bc8403fa0265f) by [Unito](https://www.unito.io) |
||
|
|
125c11b3d0 |
fix: translate blueprint label (#11573)
## Summary Fixes hardcoded "Blueprint" text and adds e2e coverage to test visibility, resolving #11473 ## Changes - **What**: - add translation - add test ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11573-fix-translate-blueprint-label-34b6d73d365081009215e06be6aa1fa0) by [Unito](https://www.unito.io) |
||
|
|
17d980dbc8 |
feat: add inline-CTA nightly survey for error panel (#11591)
## Summary Adds an inline-CTA Typeform survey for the redesigned error panel, targeting nightly localhost users. Reuses the existing node-search survey infrastructure rather than introducing a parallel stack. ## Changes - **What**: - `surveyRegistry` gains optional `presentation: 'floating' | 'inline-cta'` and a `getFloatingSurveys()` helper; controller filters by it. - `NightlySurveyPopover` accepts `mode` + `v-model:open`. Manual mode skips the eligibility watcher, drops the "Not Now" button, and leaves open/close/markSeen to the parent. - New `ErrorPanelSurveyCta.vue` renders a CTA in the error tab footer once `useExecutionErrorStore.hasAnyError` has transitioned `null → value` at least 3 times. - Popover lives in `NightlySurveyController` (session-persistent). Shared state via module-level singleton (`useErrorSurveyPopoverState`) so the iframe survives error-tab unmounts during workflow switches. - `useTypeformEmbed` centralises script loading (singleton Promise, 10s timeout, explicit `window.tf.load()` on each new container). Necessary because the embed's DOMContentLoaded auto-scan only fires once; late consumers need an explicit re-scan to render. - CTA and feedback-gate strings added under `errorPanelSurvey.*`. ## Review Focus - Manual-mode flow in `NightlySurveyPopover.vue` — CTA click is routed through the module singleton; popover stays mounted after the first open (`hasOpenedOnce` + `v-show`) to preserve the iframe across repeated open/close cycles and workflow switches. - `useTypeformEmbed.ts` — the `window.tf.load()` trick (verified against the CDN `embed.js`) is what lets two surveys coexist; without it Typeform's one-shot DOM scan misses whichever element mounts second. - `NightlySurveyController.vue` guards against double-mount by requiring `presentation === 'inline-cta'` on `errorPanelConfig`. - Scope is nightly+localhost only (`isNightlyLocalhost` in `useSurveyEligibility`); async component gate in `TabErrors.vue` keeps this out of Cloud/Desktop/stable bundles. ## Test plan - [x] `IS_NIGHTLY=true pnpm dev`, clear `Comfy.SurveyState` + `Comfy.FeatureUsage`, trigger 3 failed runs → CTA appears in error tab footer. - [x] Click "Give feedback" → Typeform popover opens and renders the form. - [x] Close popover, switch workflow (error tab unmounts), trigger a new error → CTA reappears and reopening the popover shows the same iframe. - [x] Open error-panel popover, close, then trigger 3 node searches → node-search auto-popup renders its own iframe correctly (two surveys coexist). - [x] CTA × dismisses and persists (`seenSurveys['error-panel']`). - [x] "Don't ask again" inside popover sets `optedOut: true` and hides all nightly surveys. - [x] Cloud/Desktop/stable builds: CTA never renders, controller's manual popover doesn't mount. ## Screenshot https://github.com/user-attachments/assets/91145f23-fd1e-4caf-b6cc-4b97d33ed6b7 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11591-feat-add-inline-CTA-nightly-survey-for-error-panel-34c6d73d3650817d9f95fddcf64633de) by [Unito](https://www.unito.io) |
||
|
|
bba3c38ae1 |
test: E2E coverage for painter widget (Levels 1–5) (#11551)
## Summary
Adds 7 new E2E tests for the Painter widget covering the remaining
untested items from Levels 1–5 of the widget test plan. All new tests
pass typecheck, lint, and the full pre-commit hook suite.
## Changes
**`browser_tests/tests/painter.spec.ts`**
- **Level 1.2** — assert node size ≥ 450×550 via the graph API to verify
the painter extension enforces its minimum dimensions
- **Level 1.3** — assert `width`, `height`, and `bg_color` widgets have
`options.hidden = true` so they don't appear as standard widget controls
- **Level 2.4** — cursor element becomes visible on pointer enter, its
CSS transform updates as the mouse moves across the canvas, and it hides
on pointer leave; uses `expect.poll` on transform to avoid the Vue
microtask race
- **Level 4.2** — set brush color to `#ff0000` via input event, draw a
stroke, sample a 40×40 region at canvas center and assert red pixels (R
> 200, G < 50, B < 50)
- **Level 4.3** — set opacity to 50%, draw a stroke, sample pixel alpha
values and assert semi-transparency (50 < α < 230)
- **Level 4.4** — focus the hardness slider, press ArrowLeft ×10, assert
the display value changes from `100%` to `90%`
- **Level 5.4** — set background color input to `#ff0000` via input
event, assert the canvas container div has `background-color: rgb(255,
0, 0)`
**`src/components/painter/WidgetPainter.vue`**
- Added `data-testid="painter-canvas-container"` to the inner canvas
wrapper div
- Added `data-testid="painter-cursor"` to the brush cursor div
- Added `data-testid="painter-bg-color-row"` to the background color
control row
- Added `data-testid="painter-hardness-value"` to the hardness display
span (mirrors the existing `painter-size-value` pattern)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to Playwright E2E coverage plus a few
`data-testid` attributes to stabilize selectors, with no functional
logic changes to the painter behavior.
>
> **Overview**
> Adds **7 new Playwright E2E tests** for the Painter widget, covering
minimum node sizing/hidden standard widgets, cursor
visibility/positioning behavior, brush hardness display updates,
color/opacity effects on rendered strokes, and background color
application.
>
> Updates `WidgetPainter.vue` to add a handful of **`data-testid`
hooks** (canvas container, cursor, background color row, hardness value)
used by the new tests.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
cc73baaf57 |
test: Test subgraph breadcrumbs (#11472)
## Summary Add test coverage to subgraph breadcrumbs ## Changes - **What**: - Add subgraph breadcrumb helpers - Add tests for entering/navigating/menu/collapsing ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11472-test-Test-subgraph-breadcrumbs-3486d73d365081e1a75bf57404eaa63b) by [Unito](https://www.unito.io) |
||
|
|
30c07dc9ec |
fix: cancel-subscription dialog renders Invalid Date for ISO fractional seconds (#11539)
## Summary `CancelSubscriptionDialogContent` calls `new Date(dateStr)` directly on both `cancelAt` and `subscription.value?.endDate`. Strict ISO 8601 parsers (Safari and some WebViews) reject fractional seconds whose length is anything other than 3 digits, so a Go-style backend timestamp such as `2026-04-18T10:04:55.6513Z` rendered as `Your access continues until Invalid Date.` in a destructive billing flow. PR #11358 already added the tolerant `parseIsoDateSafe` helper and applied it to the Secrets panel. This PR closes the same gap in the cancellation dialog and adds regression coverage that exercises the strict-parser code path (V8 alone is too lenient to fail without it). ## Changes - `CancelSubscriptionDialogContent.vue` — pipe both date sources through `parseIsoDateSafe`; collapse the two-step null check into one. When the value is missing OR unparseable, fall back to the existing `subscription.cancelDialog.endOfBillingPeriod` translation instead of emitting `Invalid Date`. - `CancelSubscriptionDialogContent.test.ts` (new) — wraps the assertions in the same `withStrictMillisecondParser` shim used by `dateTimeUtil.test.ts`, so 1-, 4-, and 9-digit fractional inputs actually exercise the broken path. Also covers the missing/unparseable fallbacks and the `cancelAt`-takes-precedence ordering. ## Red-green proof (local) Confirmed locally before splitting the commits: | Commit | `pnpm exec vitest run …CancelSubscriptionDialogContent.test.ts` | |---|---| | `c8aecd07f test: regression cover …` (test-only) | 5 failed / 1 passed — DOM rendered `"Your access continues until Invalid Date."` | | `f24cb903f fix: parse cancel-subscription dialog ISO timestamps …` | 6 passed | CI on this branch only runs on PR HEAD; happy to force-push a transient red commit if you want a recorded red CI run alongside the green one. ## Test plan - [x] `pnpm exec vitest run src/components/dialog/content/subscription/CancelSubscriptionDialogContent.test.ts` (6 passing on green) - [x] `pnpm typecheck` - [x] `pnpm exec eslint src/components/dialog/content/subscription/CancelSubscriptionDialogContent.{vue,test.ts}` - [x] `pnpm exec oxfmt --check` on changed files - [ ] Manual repro on Safari with a Go-emitted cancel timestamp (out of scope here; the unit test asserts the equivalent strict-parser behavior) ## Origin Surfaced by `/codex:adversarial-review` as the gap left after PR #11358 (Secrets panel) — the same `new Date(...)` hazard survived in a destructive billing flow with no regression coverage. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11539-fix-cancel-subscription-dialog-renders-Invalid-Date-for-ISO-fractional-seconds-34a6d73d365081e4bdd6c941afd8cef3) by [Unito](https://www.unito.io) |
||
|
|
7d1d7c8315 |
fix: remove deleted workflow from search results in sidebar (#11425)
*PR Created by the Glary-Bot Agent* --- ## Summary Deleting a workflow in the sidebar while search is active left the deleted workflow visible in the search results. Interacting with the stale entry (e.g. duplicate) caused undefined behavior. ## Root Cause `filteredWorkflows` in `BaseWorkflowsSidebarTab.vue` was a `ref` populated once per search event — a static snapshot that never reacted to store mutations. When `workflowStore.deleteWorkflow()` removed a workflow from `workflowLookup`, the search-panel's `filteredWorkflows` still held a stale reference. ## Fix Convert `filteredWorkflows` from a `ref` to a `computed` that reactively derives from `searchQuery` and `workflowStore.workflows`. This follows the same pattern already used by `filteredPersistedWorkflows` and `filteredBookmarkedWorkflows` in the same component. `handleSearch` is simplified to only manage tree expansion (its only remaining side effect). ## Test Plan Three e2e regression tests added to `workflowSearch.spec.ts`: - **Delete during search removes from results** — deletes a workflow while search is active, asserts it disappears - **Delete during search preserves siblings** — asserts all other matched workflows remain visible after one is deleted - **Clear search after delete shows correct browse view** — deletes during search, clears search, verifies browse view is consistent ## Screenshots   ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11425-fix-remove-deleted-workflow-from-search-results-in-sidebar-3476d73d365081f19ef6c6b9261a1ee9) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
739d4b6136 |
fix: move template distribution filter from v-show to data pipeline (#11418)
*PR Created by the Glary-Bot Agent* --- ## Summary - Moves distribution-based template filtering from a CSS-level `v-show` gate into the `useTemplateFiltering` composable's data pipeline, guaranteeing that templates not meant for the current distribution never reach the view layer - Fixes "Showing 19 of 419" count mismatch when only 2 templates are visible on Cloud with "Wan 2.2" filter active - Derives `availableModels` and `availableUseCases` from distribution-visible templates so filter dropdowns don't show options that only exist on other distributions - Always prunes `activeModels`/`activeUseCases` against available options to prevent stale persisted selections from causing zero-result filtering ## Root Cause The template selector dialog used `v-show="isTemplateVisibleOnDistribution(template)"` to hide templates that don't match the current distribution (cloud/desktop/local). But `filteredCount` and `totalCount` were computed upstream in the pipeline before this visual filter, so the count text showed all matching templates regardless of distribution visibility. ## Changes - **`useTemplateFiltering.ts`**: Added `visibleTemplates` computed that applies distribution filter at the top of the pipeline. All downstream computeds (`fuse`, `availableModels`, `availableUseCases`, `filteredBySearch`, counts) now operate on this distribution-filtered set. `activeModels`/`activeUseCases` always prune against available options. - **`WorkflowTemplateSelectorDialog.vue`**: Passes `distributions` ref to composable, removes `v-show` gate and `isTemplateVisibleOnDistribution` function. - **`useTemplateFiltering.test.ts`**: 10 new unit tests covering distribution filtering, filter composition (search + model + use case + runsOn), stale persisted selections, multi-distribution templates, and Mac distribution. - **`templateFilteringCount.spec.ts`**: 5 new `@cloud` e2e tests verifying count/card consistency, DOM leak prevention, and filter reset behavior with mocked template data. ## Verification - 22 unit tests passing (12 existing + 10 new) - `pnpm typecheck` clean - `pnpm typecheck:browser` clean - `oxlint` + `eslint` clean on all changed files - E2E tests tagged `@cloud` — designed for CI cloud build execution ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11418-fix-move-template-distribution-filter-from-v-show-to-data-pipeline-3476d73d365081c3ba09fc8a42eb4c9b) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |
||
|
|
ef59f46495 |
refactor: migrate cn imports from @/utils/tailwindUtil shim to @comfyorg/tailwind-utils directly (#11453)
*PR Created by the Glary-Bot Agent* --- ## Summary - Replace all `cn` / `ClassValue` imports from the `@/utils/tailwindUtil` re-export shim with direct imports from `@comfyorg/tailwind-utils` across 198 source files in `src/` and 3 in `apps/desktop-ui/` - Delete both shim files (`src/utils/tailwindUtil.ts` and `apps/desktop-ui/src/utils/tailwindUtil.ts`) - Add explicit `@comfyorg/tailwind-utils` dependency to `apps/desktop-ui/package.json` - Update documentation references in `AGENTS.md`, `docs/guidance/design-standards.md`, and `docs/guidance/vue-components.md` Fixes #11288 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11453-refactor-migrate-cn-imports-from-utils-tailwindUtil-shim-to-comfyorg-tailwind-utils--3486d73d365081ec92cce91fbf88e6e4) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
9522f68ae6 |
test: additional load3d e2e coverage (#11521)
## Summary Expands the e2e coverage for the load3d node ## Changes - **What**: - test recording, grid and background upload ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11521-test-additional-load3d-e2e-coverage-3496d73d3650814595e1eabe97448993) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
a9efd4de62 |
fix: render edit pencil icon correctly in properties panel header (#11487)
*PR Created by the Glary-Bot Agent* --- ## Summary The edit pencil button next to the selected node's name at the top of the properties panel (rightSidePanel) rendered as a dark filled square instead of a pencil icon. ## Root cause The button was given `size-4` (16×16) while the inner iconify `<i class="icon-[lucide--pencil] size-4">` was also 16×16. The icon overflowed the button and was clipped, and `content-center` has no effect on a default `<button>` element, so the icon wasn't centered either. Since iconify icons render via `background-color` masked by the SVG, a clipped mask rendered as a partial/solid block that reads as a dark square. ## Fix Remove `size-4` and `content-center` from the button; use `inline-flex items-center justify-center` so the button sizes naturally around the 16×16 icon and centers it properly. ```diff -class="relative top-[2px] ml-2 size-4 shrink-0 cursor-pointer content-center text-muted-foreground hover:text-base-foreground" +class="relative top-[2px] ml-2 inline-flex shrink-0 cursor-pointer items-center justify-center text-muted-foreground hover:text-base-foreground" ``` One-line change in `src/components/rightSidePanel/RightSidePanel.vue`. No behavior change — clicking the button still switches the title into edit mode via `isEditing = true`. Existing e2e coverage in `browser_tests/tests/propertiesPanel/titleEditing.spec.ts` exercises click behavior. ## Visual verification Before — dark filled square:  After — pencil icon renders correctly:  Full panel view after the fix:  ## Quality gates - `pnpm lint` — clean (pre-existing unrelated warning in a test file) - `pnpm typecheck` — clean - `pnpm format` — no-op - Manual verification: clicking the pencil still opens the editable title input ## Context Reported by Alex in Slack `#bug-dump`. Present in `main`, unrelated to #11414. Bug tracker: [Notion — Icon next to node name in properties panel is broken](https://www.notion.so/Bug-Icon-next-to-node-name-in-properties-panel-is-broken-3486d73d365081919d7ae96dbe260ab4) ## Screenshots    ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11487-fix-render-edit-pencil-icon-correctly-in-properties-panel-header-3496d73d36508157ba08f4a7a6e31fdd) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
ac728b92ae |
fix: fix webcam node not showing preview in nodes 2.0 (#11549)
## Summary Adds test coverage for webcam node & fixes issue found in testing where the captured image does not show in nodes 2.0 ## Changes - **What**: - call `setNodePreviewsByNodeId` alongside `node.imgs = [img]` - add tests for general coverage ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11549-fix-fix-webcam-node-not-showing-preview-in-nodes-2-0-34a6d73d3650810c89eee9c25cd07700) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
bea72410fd |
test: add unit tests for utility widgets (#11442)
## Summary Adds 26 unit tests across 3 files covering BatchNavigation, FormSearchInput, and WidgetLayoutField. Part of a widget-test-coverage sequence. ## Changes - **What**: - \`BatchNavigation.test.ts\` (10) — hidden when count ≤ 1, counter formatted as 1-based \`current / total\`, prev/next navigation, disabled states at range boundaries. - \`FormSearchInput.test.ts\` (8) — v-model binding as the user types, clear-button visibility based on trimmed-query, debounced searcher invocation with fake timers (250ms debounce, 1000ms maxWait). - \`WidgetLayoutField.test.ts\` (8) — widget.name vs widget.label preference, empty-name suppression, \`HideLayoutFieldKey\` injection hides label but preserves slot, slot receives \`borderStyle\` scoped prop. ## Review Focus - Fake timers used in FormSearchInput tests for \`refDebounced\` — the debounce assertion depends on the 250ms/1000ms window in the component staying unchanged. - \`HideLayoutFieldKey\` provided via \`global.provide\` using the Symbol key. - No changes to any source component. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11442-test-add-unit-tests-for-utility-widgets-3486d73d365081a891cafe21b09b91c0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: bymyself <cbyrne@comfy.org> |
||
|
|
2c772077e0 |
test: add E2E tests for billing dialogs (CancelSubscription, TopUpCredits) (#10969)
## Summary - Add Playwright E2E tests for `CancelSubscriptionDialogContent` and `TopUpCreditsDialogContentLegacy` - CancelSubscription tests: dialog display with date formatting, keep subscription dismiss, confirm cancel with mocked API, error handling on API failure - TopUpCredits tests: dialog display with preset amounts, insufficient credits variant, preset selection, close button dismiss, pricing link visibility Part of the FixIt Burndown test coverage initiative (Untested Dialogs). ## Test plan - [ ] Verify tests pass in CI against OSS build - [ ] `pnpm test:browser:local -- browser_tests/tests/dialogs/cancelSubscriptionDialog.spec.ts` - [ ] `pnpm test:browser:local -- browser_tests/tests/dialogs/topUpCreditsDialog.spec.ts` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10969-test-add-E2E-tests-for-billing-dialogs-CancelSubscription-TopUpCredits-33c6d73d36508164b268c08c99464ca1) by [Unito](https://www.unito.io) |
||
|
|
00c294297e |
test: add WidgetImageCrop unit tests (#11470)
## Summary Splits the WidgetImageCrop test coverage out of #11446 so this widget can be reviewed independently. ## Changes - **What**: Adds WidgetImageCrop unit tests covering empty/loading/loaded states, ratio-control gating, bounding-box delegation, and disabled upstream behavior. ## Review Focus Focused test-only PR extracted from #11446. Includes small test-only cleanups from the earlier review: shared crop mock defaults, accessible image querying, and reactive upstream mock setup. Validated with `pnpm test:unit -- --run src/components/imagecrop/WidgetImageCrop.test.ts`. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11470-test-add-WidgetImageCrop-unit-tests-3486d73d365081ff9a1eed159a8eb9a3) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
65e27b5cdf |
test: add unit tests for graph-level widgets (#11445)
## Summary Adds 22 unit tests across 3 files covering WidgetDOM, MultiSelectWidget, and TextPreviewWidget. Part of a widget-test-coverage sequence. ## Changes - **What**: - \`WidgetDOM.test.ts\` (4) — mounts the resolved DOMWidget element into the container, empty container when no host node resolves, skips mount when resolved widget is not a DOM widget, visible root for pointer-event capture. - \`MultiSelectWidget.test.ts\` (8) — forwards \`inputSpec.options\`, falls back to empty options, placeholder from \`multi_select.placeholder\`, default placeholder, chip vs comma display, initial selection forwarding. - \`TextPreviewWidget.test.ts\` (10) — plain text, newline→\`<br>\`, bare-URL auto-linking, \`[[label|url]]\` http link with target/rel safety, non-http falls back to escaped label (XSS-safe), skeleton visibility transitions via mocked executionStore. ## Review Focus - \`WidgetDOM\` mocks \`useCanvasStore\`, \`resolveWidgetFromHostNode\`, and \`isDOMWidget\` at the module boundary; test asserts identity of the mounted element (same \`HTMLElement\` reference) rather than canvas-side-effects. - \`TextPreviewWidget\` replaces \`useExecutionStore\` with a \`reactive()\` proxy held in a hoisted holder so watcher assertions see real reactive mutations (plain \`vi.hoisted\` objects don't trigger Vue effects). - No changes to any source component. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11445-test-add-unit-tests-for-graph-level-widgets-3486d73d3650816180d5f31a523f5c22) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: bymyself <cbyrne@comfy.org> |
||
|
|
dd16e7a9ea |
test: add WidgetBoundingBox unit tests (#11468)
## Summary Splits the WidgetBoundingBox test coverage out of #11446 so this widget can be reviewed independently. ## Changes - **What**: Adds WidgetBoundingBox unit tests covering labels, initial values, min constraints, immutable v-model updates, and disabled propagation. ## Review Focus Focused test-only PR extracted from #11446. Validated with `pnpm test:unit -- --run src/components/boundingbox/WidgetBoundingBox.test.ts`. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11468-test-add-WidgetBoundingBox-unit-tests-3486d73d365081a682f8c5090e376ec6) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
9ed7a7bd87 |
test: Add tests for help center (#11475)
## Summary Test coverage for help center & associated popups ## Changes - **What**: - Adds HelpCenterHelper for mocking endpoints and locators - Tests for popup, menu items & positioning ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11475-test-Add-tests-for-help-center-3486d73d365081af91a2eb7465e503fe) by [Unito](https://www.unito.io) |
||
|
|
78630f5485 |
test: add WidgetRange unit tests (#11471)
## Summary Splits the WidgetRange test coverage out of #11446 so this widget can be reviewed independently. ## Changes - **What**: Adds WidgetRange unit tests covering value pass-through, display propagation, disabled-state handling, upstream overrides, and histogram derivation. ## Review Focus Focused test-only PR extracted from #11446. Validated with `pnpm test:unit -- --run src/components/range/WidgetRange.test.ts`. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11471-test-add-WidgetRange-unit-tests-3486d73d365081d7a684ca3ff02320d6) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |